NOISSUE reorganize and document libraries
This commit is contained in:
145
libraries/README.md
Normal file
145
libraries/README.md
Normal file
@ -0,0 +1,145 @@
|
||||
# Third-party libraries
|
||||
|
||||
This folder has third-party or otherwise external libraries needed for other parts to work.
|
||||
|
||||
## classparser
|
||||
A simplistic parser for Java class files.
|
||||
|
||||
This library has served as a base for some (much more full-featured and advanced) work under NDA for AVG. It, however, should NOT be confused with that work.
|
||||
|
||||
Copyright belongs to Petr Mrázek, unless explicitly stated otherwise in the source files. Available under the Apache 2.0 license.
|
||||
|
||||
## hoedown
|
||||
Hoedown is a revived fork of Sundown, the Markdown parser based on the original code of the Upskirt library by Natacha Porté.
|
||||
|
||||
See [github repo](https://github.com/hoedown/hoedown).
|
||||
|
||||
## iconfix
|
||||
This was originally part of the razor-qt project and the Qt toolkit, respecitvely. Its sole purpose is to reimplement Qt's icon loading logic to prevent it from using any platform plugins that could break icon loading.
|
||||
|
||||
Licensed under LGPL 2.1
|
||||
|
||||
## javacheck
|
||||
Simple Java tool that prints the JVM details - version and platform bitness.
|
||||
|
||||
Do what you want with it. It is so trivial that noone cares.
|
||||
|
||||
## launcher
|
||||
Java launcher part for Minecraft.
|
||||
|
||||
It:
|
||||
* Starts a process
|
||||
* Waits for a launch script on stdin
|
||||
* Consumes the launch script you feed it
|
||||
* Proceeds with launch when it gets the `launcher` command
|
||||
|
||||
This means the process is essentially idle until the final command is sent. You can, for example, attach a profiler before you send it.
|
||||
|
||||
A `legacy` and `onesix` launchers are available.
|
||||
|
||||
* `legacy` is intended for use with Minecraft versions < 1.6 and is deprecated.
|
||||
* `onesix` can handle launching any Minecraft version, at the cost of some extra features `legacy` enables (custom window icon and title).
|
||||
|
||||
Example (some parts have been censored):
|
||||
```
|
||||
mod legacyjavafixer-1.0
|
||||
mainClass net.minecraft.launchwrapper.Launch
|
||||
param --username
|
||||
param CENSORED
|
||||
param --version
|
||||
param MultiMC5
|
||||
param --gameDir
|
||||
param /home/peterix/minecraft/FTB/17ForgeTest/minecraft
|
||||
param --assetsDir
|
||||
param /home/peterix/minecraft/mmc5/assets
|
||||
param --assetIndex
|
||||
param 1.7.10
|
||||
param --uuid
|
||||
param CENSORED
|
||||
param --accessToken
|
||||
param CENSORED
|
||||
param --userProperties
|
||||
param {}
|
||||
param --userType
|
||||
param mojang
|
||||
param --tweakClass
|
||||
param cpw.mods.fml.common.launcher.FMLTweaker
|
||||
windowTitle MultiMC: 172ForgeTest
|
||||
windowParams 854x480
|
||||
userName CENSORED
|
||||
sessionId token:CENSORED:CENSORED
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/mojang/realms/1.3.5/realms-1.3.5.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/apache/commons/commons-compress/1.8.1/commons-compress-1.8.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/apache/httpcomponents/httpclient/4.3.3/httpclient-4.3.3.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/apache/httpcomponents/httpcore/4.3.2/httpcore-4.3.2.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/java3d/vecmath/1.3.1/vecmath-1.3.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/net/sf/trove4j/trove4j/3.0.3/trove4j-3.0.3.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/ibm/icu/icu4j-core-mojang/51.2/icu4j-core-mojang-51.2.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/net/sf/jopt-simple/jopt-simple/4.5/jopt-simple-4.5.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/paulscode/codecjorbis/20101023/codecjorbis-20101023.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/paulscode/codecwav/20101023/codecwav-20101023.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/paulscode/libraryjavasound/20101123/libraryjavasound-20101123.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/paulscode/librarylwjglopenal/20100824/librarylwjglopenal-20100824.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/paulscode/soundsystem/20120107/soundsystem-20120107.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/io/netty/netty-all/4.0.10.Final/netty-all-4.0.10.Final.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/google/guava/guava/16.0/guava-16.0.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/apache/commons/commons-lang3/3.2.1/commons-lang3-3.2.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/commons-io/commons-io/2.4/commons-io-2.4.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/commons-codec/commons-codec/1.9/commons-codec-1.9.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/google/code/gson/gson/2.2.4/gson-2.2.4.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/mojang/authlib/1.5.16/authlib-1.5.16.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/lwjgl/lwjgl/lwjgl/2.9.1/lwjgl-2.9.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/lwjgl/lwjgl/lwjgl_util/2.9.1/lwjgl_util-2.9.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/tv/twitch/twitch/5.16/twitch-5.16.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/net/minecraftforge/forge/1.7.10-10.13.0.1178/forge-1.7.10-10.13.0.1178.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/net/minecraft/launchwrapper/1.9/launchwrapper-1.9.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/ow2/asm/asm-all/4.1/asm-all-4.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/typesafe/akka/akka-actor_2.11/2.3.3/akka-actor_2.11-2.3.3.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/com/typesafe/config/1.2.1/config-1.2.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-actors-migration_2.11/1.1.0/scala-actors-migration_2.11-1.1.0.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-compiler/2.11.1/scala-compiler-2.11.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/plugins/scala-continuations-library_2.11/1.0.2/scala-continuations-library_2.11-1.0.2.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/plugins/scala-continuations-plugin_2.11.1/1.0.2/scala-continuations-plugin_2.11.1-1.0.2.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-library/2.11.1/scala-library-2.11.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-parser-combinators_2.11/1.0.1/scala-parser-combinators_2.11-1.0.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-reflect/2.11.1/scala-reflect-2.11.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-swing_2.11/1.0.1/scala-swing_2.11-1.0.1.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/org/scala-lang/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar
|
||||
cp /home/peterix/minecraft/FTB/libraries/lzma/lzma/0.0.1/lzma-0.0.1.jar
|
||||
ext /home/peterix/minecraft/FTB/libraries/org/lwjgl/lwjgl/lwjgl-platform/2.9.1/lwjgl-platform-2.9.1-natives-linux.jar
|
||||
ext /home/peterix/minecraft/FTB/libraries/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar
|
||||
natives /home/peterix/minecraft/FTB/17ForgeTest/natives
|
||||
cp /home/peterix/minecraft/FTB/versions/1.7.10/1.7.10.jar
|
||||
launcher onesix
|
||||
```
|
||||
|
||||
Available under the Apache 2.0 license.
|
||||
|
||||
## libnbtplusplus
|
||||
libnbt++ is a free C++ library for Minecraft's file format Named Binary Tag (NBT). It can read and write compressed and uncompressed NBT files and provides a code interface for working with NBT data.
|
||||
|
||||
See [github repo](https://github.com/ljfa-ag/libnbtplusplus).
|
||||
|
||||
Available either under LGPL version 3 or later.
|
||||
|
||||
## pack200
|
||||
Unpacks pack200 archives (squished, compression-optimized Java jars). This format is only used by Forge to save bandwidth.
|
||||
|
||||
A horrible little thing extracted from the depths of the OpenJDK codebase. Please don't look at it, or you will praise Cthulhu for his clean code for the rest of your days.
|
||||
|
||||
Available under GPL 2 with classpath exception.
|
||||
|
||||
## rainbow
|
||||
Color functions extracted from [KGuiAddons](https://inqlude.org/libraries/kguiaddons.html). Used for adaptive text coloring.
|
||||
|
||||
Available either under LGPL version 2.1 or later.
|
||||
|
||||
## xz-embedded
|
||||
Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth.
|
||||
|
||||
Public domain.
|
41
libraries/classparser/CMakeLists.txt
Normal file
41
libraries/classparser/CMakeLists.txt
Normal file
@ -0,0 +1,41 @@
|
||||
project(classparser)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
######## Check endianness ########
|
||||
include(TestBigEndian)
|
||||
test_big_endian(BIGENDIAN)
|
||||
if(${BIGENDIAN})
|
||||
add_definitions(-DMULTIMC_BIG_ENDIAN)
|
||||
endif(${BIGENDIAN})
|
||||
|
||||
# Find Qt
|
||||
find_package(Qt5Core REQUIRED)
|
||||
|
||||
# Include Qt headers.
|
||||
include_directories(${Qt5Base_INCLUDE_DIRS})
|
||||
|
||||
set(CLASSPARSER_HEADERS
|
||||
# Public headers
|
||||
include/classparser_config.h
|
||||
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
|
||||
)
|
||||
|
||||
add_definitions(-DCLASSPARSER_LIBRARY)
|
||||
|
||||
add_library(classparser SHARED ${CLASSPARSER_SOURCES} ${CLASSPARSER_HEADERS})
|
||||
target_include_directories(classparser PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
qt5_use_modules(classparser Core)
|
@ -13,13 +13,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
#include "minecraft/Mod.h"
|
||||
|
||||
class MinecraftForge : public Mod
|
||||
{
|
||||
public:
|
||||
MinecraftForge(const QString &file);
|
||||
bool FixVersionIfNeeded(QString newVersion);
|
||||
};
|
||||
#ifdef CLASSPARSER_LIBRARY
|
||||
#define CLASSPARSER_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define CLASSPARSER_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
@ -1,4 +1,6 @@
|
||||
/* Copyright 2013-2015 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.
|
||||
@ -12,26 +14,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
enum OpSys
|
||||
#include "classparser_config.h"
|
||||
|
||||
#define MCVer_Unknown "Unknown"
|
||||
|
||||
namespace javautils
|
||||
{
|
||||
Os_Windows,
|
||||
Os_Linux,
|
||||
Os_OSX,
|
||||
Os_Other
|
||||
};
|
||||
|
||||
OpSys OpSys_fromString(QString);
|
||||
QString OpSys_toString(OpSys);
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#define currentSystem Os_Windows
|
||||
#else
|
||||
#ifdef Q_OS_MAC
|
||||
#define currentSystem Os_OSX
|
||||
#else
|
||||
#define currentSystem Os_Linux
|
||||
#endif
|
||||
#endif
|
||||
/**
|
||||
* @brief Get the version from a minecraft.jar by parsing its class files. Expensive!
|
||||
*/
|
||||
QString GetMinecraftJarVersion(QString jar);
|
||||
}
|
85
libraries/classparser/src/annotations.cpp
Normal file
85
libraries/classparser/src/annotations.cpp
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
277
libraries/classparser/src/annotations.h
Normal file
277
libraries/classparser/src/annotations.h
Normal file
@ -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";
|
||||
}
|
||||
;
|
||||
};
|
||||
}
|
156
libraries/classparser/src/classfile.h
Normal file
156
libraries/classparser/src/classfile.h
Normal file
@ -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;
|
||||
};
|
||||
}
|
220
libraries/classparser/src/constants.h
Normal file
220
libraries/classparser/src/constants.h
Normal file
@ -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;
|
||||
};
|
||||
}
|
8
libraries/classparser/src/errors.h
Normal file
8
libraries/classparser/src/errors.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include <exception>
|
||||
namespace java
|
||||
{
|
||||
class classfile_exception : public std::exception
|
||||
{
|
||||
};
|
||||
}
|
76
libraries/classparser/src/javaendian.h
Normal file
76
libraries/classparser/src/javaendian.h
Normal file
@ -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
|
||||
}
|
83
libraries/classparser/src/javautils.cpp
Normal file
83
libraries/classparser/src/javautils.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
/* Copyright 2013-2015 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;
|
||||
}
|
||||
}
|
63
libraries/classparser/src/membuffer.h
Normal file
63
libraries/classparser/src/membuffer.h
Normal file
@ -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;
|
||||
};
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
project(MultiMC_logic)
|
||||
|
||||
set(GUI_SOURCES
|
||||
DesktopServices.h
|
||||
DesktopServices.cpp
|
||||
|
||||
# Icons
|
||||
icons/MMCIcon.h
|
||||
icons/MMCIcon.cpp
|
||||
icons/IconList.h
|
||||
icons/IconList.cpp
|
||||
|
||||
SkinUtils.cpp
|
||||
SkinUtils.h
|
||||
)
|
||||
################################ COMPILE ################################
|
||||
|
||||
add_library(MultiMC_gui SHARED ${GUI_SOURCES})
|
||||
set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
|
||||
|
||||
generate_export_header(MultiMC_gui)
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC_gui iconfix MultiMC_logic)
|
||||
qt5_use_modules(MultiMC_gui Gui)
|
||||
|
||||
# Mark and export headers
|
||||
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
|
@ -1,149 +0,0 @@
|
||||
#include "DesktopServices.h"
|
||||
#include <QDir>
|
||||
#include <QDesktopServices>
|
||||
#include <QProcess>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
|
||||
*/
|
||||
#if defined(Q_OS_LINUX)
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
template <typename T>
|
||||
bool IndirectOpen(T callable, qint64 *pid_forked = nullptr)
|
||||
{
|
||||
auto pid = fork();
|
||||
if(pid_forked)
|
||||
{
|
||||
if(pid > 0)
|
||||
*pid_forked = pid;
|
||||
else
|
||||
*pid_forked = 0;
|
||||
}
|
||||
if(pid == -1)
|
||||
{
|
||||
qWarning() << "IndirectOpen failed to fork: " << errno;
|
||||
return false;
|
||||
}
|
||||
// child - do the stuff
|
||||
if(pid == 0)
|
||||
{
|
||||
// unset all this garbage so it doesn't get passed to the child process
|
||||
qunsetenv("LD_PRELOAD");
|
||||
qunsetenv("LD_LIBRARY_PATH");
|
||||
qunsetenv("LD_DEBUG");
|
||||
qunsetenv("QT_PLUGIN_PATH");
|
||||
qunsetenv("QT_FONTPATH");
|
||||
|
||||
// open the URL
|
||||
auto status = callable();
|
||||
|
||||
// detach from the parent process group.
|
||||
setsid();
|
||||
|
||||
// die. now. do not clean up anything, it would just hang forever.
|
||||
_exit(status ? 0 : 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
//parent - assume it worked.
|
||||
int status;
|
||||
while (waitpid(pid, &status, 0))
|
||||
{
|
||||
if(WIFEXITED(status))
|
||||
{
|
||||
return WEXITSTATUS(status) == 0;
|
||||
}
|
||||
if(WIFSIGNALED(status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace DesktopServices {
|
||||
bool openDirectory(const QString &path, bool ensureExists)
|
||||
{
|
||||
qDebug() << "Opening directory" << path;
|
||||
QDir parentPath;
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
{
|
||||
parentPath.mkpath(dir.absolutePath());
|
||||
}
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
|
||||
};
|
||||
#if defined(Q_OS_LINUX)
|
||||
return IndirectOpen(f);
|
||||
#else
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openFile(const QString &path)
|
||||
{
|
||||
qDebug() << "Opening file" << path;
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
};
|
||||
#if defined(Q_OS_LINUX)
|
||||
return IndirectOpen(f);
|
||||
#else
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid)
|
||||
{
|
||||
qDebug() << "Opening file" << path << "using" << application;
|
||||
#if defined(Q_OS_LINUX)
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
return IndirectOpen([&]()
|
||||
{
|
||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory);
|
||||
}, pid);
|
||||
#else
|
||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool run(const QString &application, const QStringList &args, const QString &workingDirectory, qint64 *pid)
|
||||
{
|
||||
qDebug() << "Running" << application << "with args" << args.join(' ');
|
||||
#if defined(Q_OS_LINUX)
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
return IndirectOpen([&]()
|
||||
{
|
||||
return QProcess::startDetached(application, args, workingDirectory);
|
||||
}, pid);
|
||||
#else
|
||||
return QProcess::startDetached(application, args, workingDirectory, pid);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openUrl(const QUrl &url)
|
||||
{
|
||||
qDebug() << "Opening URL" << url.toString();
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(url);
|
||||
};
|
||||
#if defined(Q_OS_LINUX)
|
||||
return IndirectOpen(f);
|
||||
#else
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QUrl>
|
||||
#include <QString>
|
||||
#include "multimc_gui_export.h"
|
||||
|
||||
/**
|
||||
* This wraps around QDesktopServices and adds workarounds where needed
|
||||
* Use this instead of QDesktopServices!
|
||||
*/
|
||||
namespace DesktopServices
|
||||
{
|
||||
/**
|
||||
* Open a file in whatever application is applicable
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openFile(const QString &path);
|
||||
|
||||
/**
|
||||
* Open a file in the specified application
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
|
||||
|
||||
/**
|
||||
* Run an application
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
|
||||
|
||||
/**
|
||||
* Open a directory
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false);
|
||||
|
||||
/**
|
||||
* Open the URL, most likely in a browser. Maybe.
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
|
||||
};
|
@ -1,47 +0,0 @@
|
||||
/* Copyright 2013-2015 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 "SkinUtils.h"
|
||||
#include "net/HttpMetaCache.h"
|
||||
#include "Env.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace SkinUtils
|
||||
{
|
||||
/*
|
||||
* Given a username, return a pixmap of the cached skin (if it exists), QPixmap() otherwise
|
||||
*/
|
||||
QPixmap getFaceFromCache(QString username, int height, int width)
|
||||
{
|
||||
QFile fskin(ENV.metacache()
|
||||
->resolveEntry("skins", username + ".png")
|
||||
->getFullPath());
|
||||
|
||||
if (fskin.exists())
|
||||
{
|
||||
QPixmap skin(fskin.fileName());
|
||||
if(!skin.isNull())
|
||||
{
|
||||
return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
return QPixmap();
|
||||
}
|
||||
}
|
@ -1,381 +0,0 @@
|
||||
/* Copyright 2013-2015 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 "IconList.h"
|
||||
#include <FileSystem.h>
|
||||
#include <QMap>
|
||||
#include <QEventLoop>
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QSet>
|
||||
#include <QDebug>
|
||||
|
||||
#define MAX_SIZE 1024
|
||||
|
||||
IconList::IconList(QString builtinPath, QString path, QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
// add builtin icons
|
||||
QDir instance_icons(builtinPath);
|
||||
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
|
||||
for (auto file_info : file_info_list)
|
||||
{
|
||||
QString key = file_info.baseName();
|
||||
addIcon(key, key, file_info.absoluteFilePath(), MMCIcon::Builtin);
|
||||
}
|
||||
|
||||
m_watcher.reset(new QFileSystemWatcher());
|
||||
is_watching = false;
|
||||
connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
|
||||
SLOT(directoryChanged(QString)));
|
||||
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
|
||||
directoryChanged(path);
|
||||
}
|
||||
|
||||
void IconList::directoryChanged(const QString &path)
|
||||
{
|
||||
QDir new_dir (path);
|
||||
if(m_dir.absolutePath() != new_dir.absolutePath())
|
||||
{
|
||||
m_dir.setPath(path);
|
||||
m_dir.refresh();
|
||||
if(is_watching)
|
||||
stopWatching();
|
||||
startWatching();
|
||||
}
|
||||
if(!m_dir.exists())
|
||||
if(!FS::ensureFolderPathExists(m_dir.absolutePath()))
|
||||
return;
|
||||
m_dir.refresh();
|
||||
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
|
||||
for (auto it = new_list.begin(); it != new_list.end(); it++)
|
||||
{
|
||||
QString &foo = (*it);
|
||||
foo = m_dir.filePath(foo);
|
||||
}
|
||||
auto new_set = new_list.toSet();
|
||||
QList<QString> current_list;
|
||||
for (auto &it : icons)
|
||||
{
|
||||
if (!it.has(MMCIcon::FileBased))
|
||||
continue;
|
||||
current_list.push_back(it.m_images[MMCIcon::FileBased].filename);
|
||||
}
|
||||
QSet<QString> current_set = current_list.toSet();
|
||||
|
||||
QSet<QString> to_remove = current_set;
|
||||
to_remove -= new_set;
|
||||
|
||||
QSet<QString> to_add = new_set;
|
||||
to_add -= current_set;
|
||||
|
||||
for (auto remove : to_remove)
|
||||
{
|
||||
qDebug() << "Removing " << remove;
|
||||
QFileInfo rmfile(remove);
|
||||
QString key = rmfile.baseName();
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
continue;
|
||||
icons[idx].remove(MMCIcon::FileBased);
|
||||
if (icons[idx].type() == MMCIcon::ToBeDeleted)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
icons.remove(idx);
|
||||
reindex();
|
||||
endRemoveRows();
|
||||
}
|
||||
else
|
||||
{
|
||||
dataChanged(index(idx), index(idx));
|
||||
}
|
||||
m_watcher->removePath(remove);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
|
||||
for (auto add : to_add)
|
||||
{
|
||||
qDebug() << "Adding " << add;
|
||||
QFileInfo addfile(add);
|
||||
QString key = addfile.baseName();
|
||||
if (addIcon(key, QString(), addfile.filePath(), MMCIcon::FileBased))
|
||||
{
|
||||
m_watcher->addPath(add);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IconList::fileChanged(const QString &path)
|
||||
{
|
||||
qDebug() << "Checking " << path;
|
||||
QFileInfo checkfile(path);
|
||||
if (!checkfile.exists())
|
||||
return;
|
||||
QString key = checkfile.baseName();
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
return;
|
||||
QIcon icon(path);
|
||||
if (!icon.availableSizes().size())
|
||||
return;
|
||||
|
||||
icons[idx].m_images[MMCIcon::FileBased].icon = icon;
|
||||
dataChanged(index(idx), index(idx));
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
|
||||
void IconList::SettingChanged(const Setting &setting, QVariant value)
|
||||
{
|
||||
if(setting.id() != "IconsDir")
|
||||
return;
|
||||
|
||||
directoryChanged(value.toString());
|
||||
}
|
||||
|
||||
void IconList::startWatching()
|
||||
{
|
||||
auto abs_path = m_dir.absolutePath();
|
||||
FS::ensureFolderPathExists(abs_path);
|
||||
is_watching = m_watcher->addPath(abs_path);
|
||||
if (is_watching)
|
||||
{
|
||||
qDebug() << "Started watching " << abs_path;
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Failed to start watching " << abs_path;
|
||||
}
|
||||
}
|
||||
|
||||
void IconList::stopWatching()
|
||||
{
|
||||
m_watcher->removePaths(m_watcher->files());
|
||||
m_watcher->removePaths(m_watcher->directories());
|
||||
is_watching = false;
|
||||
}
|
||||
|
||||
QStringList IconList::mimeTypes() const
|
||||
{
|
||||
QStringList types;
|
||||
types << "text/uri-list";
|
||||
return types;
|
||||
}
|
||||
Qt::DropActions IconList::supportedDropActions() const
|
||||
{
|
||||
return Qt::CopyAction;
|
||||
}
|
||||
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
// check if the action is supported
|
||||
if (!data || !(action & supportedDropActions()))
|
||||
return false;
|
||||
|
||||
// files dropped from outside?
|
||||
if (data->hasUrls())
|
||||
{
|
||||
auto urls = data->urls();
|
||||
QStringList iconFiles;
|
||||
for (auto url : urls)
|
||||
{
|
||||
// only local files may be dropped...
|
||||
if (!url.isLocalFile())
|
||||
continue;
|
||||
iconFiles += url.toLocalFile();
|
||||
}
|
||||
installIcons(iconFiles);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::ItemFlags IconList::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
else
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
}
|
||||
|
||||
QVariant IconList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
int row = index.row();
|
||||
|
||||
if (row < 0 || row >= icons.size())
|
||||
return QVariant();
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DecorationRole:
|
||||
return icons[row].icon();
|
||||
case Qt::DisplayRole:
|
||||
return icons[row].name();
|
||||
case Qt::UserRole:
|
||||
return icons[row].m_key;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int IconList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return icons.size();
|
||||
}
|
||||
|
||||
void IconList::installIcons(QStringList iconFiles)
|
||||
{
|
||||
for (QString file : iconFiles)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
continue;
|
||||
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
|
||||
|
||||
QString suffix = fileinfo.suffix();
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
|
||||
continue;
|
||||
|
||||
if (!QFile::copy(file, target))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
bool IconList::iconFileExists(QString key)
|
||||
{
|
||||
auto iconEntry = icon(key);
|
||||
if(!iconEntry)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return iconEntry->has(MMCIcon::FileBased);
|
||||
}
|
||||
|
||||
const MMCIcon *IconList::icon(QString key)
|
||||
{
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
return nullptr;
|
||||
return &icons[iconIdx];
|
||||
}
|
||||
|
||||
bool IconList::deleteIcon(QString key)
|
||||
{
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
return false;
|
||||
auto &iconEntry = icons[iconIdx];
|
||||
if (iconEntry.has(MMCIcon::FileBased))
|
||||
{
|
||||
return QFile::remove(iconEntry.m_images[MMCIcon::FileBased].filename);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IconList::addIcon(QString key, QString name, QString path, MMCIcon::Type type)
|
||||
{
|
||||
// replace the icon even? is the input valid?
|
||||
QIcon icon(path);
|
||||
if (!icon.availableSizes().size())
|
||||
return false;
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end())
|
||||
{
|
||||
auto &oldOne = icons[*iter];
|
||||
oldOne.replace(type, icon, path);
|
||||
dataChanged(index(*iter), index(*iter));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
||||
{
|
||||
MMCIcon mmc_icon;
|
||||
mmc_icon.m_name = name;
|
||||
mmc_icon.m_key = key;
|
||||
mmc_icon.replace(type, icon, path);
|
||||
icons.push_back(mmc_icon);
|
||||
name_index[key] = icons.size() - 1;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void IconList::reindex()
|
||||
{
|
||||
name_index.clear();
|
||||
int i = 0;
|
||||
for (auto &iter : icons)
|
||||
{
|
||||
name_index[iter.m_key] = i;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
QIcon IconList::getIcon(QString key)
|
||||
{
|
||||
int icon_index = getIconIndex(key);
|
||||
|
||||
if (icon_index != -1)
|
||||
return icons[icon_index].icon();
|
||||
|
||||
// Fallback for icons that don't exist.
|
||||
icon_index = getIconIndex("infinity");
|
||||
|
||||
if (icon_index != -1)
|
||||
return icons[icon_index].icon();
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
QIcon IconList::getBigIcon(QString key)
|
||||
{
|
||||
int icon_index = getIconIndex(key);
|
||||
|
||||
if (icon_index == -1)
|
||||
key = "infinity";
|
||||
|
||||
// Fallback for icons that don't exist.
|
||||
icon_index = getIconIndex(key);
|
||||
|
||||
if (icon_index == -1)
|
||||
return QIcon();
|
||||
|
||||
QPixmap bigone = icons[icon_index].icon().pixmap(256,256).scaled(256,256);
|
||||
return QIcon(bigone);
|
||||
}
|
||||
|
||||
int IconList::getIconIndex(QString key)
|
||||
{
|
||||
if (key == "default")
|
||||
key = "infinity";
|
||||
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end())
|
||||
return *iter;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
//#include "IconList.moc"
|
@ -1,85 +0,0 @@
|
||||
/* Copyright 2013-2015 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 <QMutex>
|
||||
#include <QAbstractListModel>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QtGui/QIcon>
|
||||
#include <memory>
|
||||
#include "MMCIcon.h"
|
||||
#include "settings/Setting.h"
|
||||
#include "Env.h" // there is a global icon list inside Env.
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QFileSystemWatcher;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT IconList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IconList(QString builtinPath, QString path, QObject *parent = 0);
|
||||
virtual ~IconList() {};
|
||||
|
||||
QIcon getIcon(QString key);
|
||||
QIcon getBigIcon(QString key);
|
||||
int getIconIndex(QString key);
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
|
||||
bool addIcon(QString key, QString name, QString path, MMCIcon::Type type);
|
||||
bool deleteIcon(QString key);
|
||||
bool iconFileExists(QString key);
|
||||
|
||||
virtual QStringList mimeTypes() const;
|
||||
virtual Qt::DropActions supportedDropActions() const;
|
||||
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent);
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
|
||||
void installIcons(QStringList iconFiles);
|
||||
|
||||
const MMCIcon * icon(QString key);
|
||||
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
||||
signals:
|
||||
void iconUpdated(QString key);
|
||||
|
||||
private:
|
||||
// hide copy constructor
|
||||
IconList(const IconList &) = delete;
|
||||
// hide assign op
|
||||
IconList &operator=(const IconList &) = delete;
|
||||
void reindex();
|
||||
|
||||
public slots:
|
||||
void directoryChanged(const QString &path);
|
||||
|
||||
protected slots:
|
||||
void fileChanged(const QString &path);
|
||||
void SettingChanged(const Setting & setting, QVariant value);
|
||||
private:
|
||||
std::shared_ptr<QFileSystemWatcher> m_watcher;
|
||||
bool is_watching;
|
||||
QMap<QString, int> name_index;
|
||||
QVector<MMCIcon> icons;
|
||||
QDir m_dir;
|
||||
};
|
@ -1,89 +0,0 @@
|
||||
/* Copyright 2013-2015 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 "MMCIcon.h"
|
||||
#include <QFileInfo>
|
||||
|
||||
MMCIcon::Type operator--(MMCIcon::Type &t, int)
|
||||
{
|
||||
MMCIcon::Type temp = t;
|
||||
switch (t)
|
||||
{
|
||||
case MMCIcon::Type::Builtin:
|
||||
t = MMCIcon::Type::ToBeDeleted;
|
||||
break;
|
||||
case MMCIcon::Type::Transient:
|
||||
t = MMCIcon::Type::Builtin;
|
||||
break;
|
||||
case MMCIcon::Type::FileBased:
|
||||
t = MMCIcon::Type::Transient;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
MMCIcon::Type MMCIcon::type() const
|
||||
{
|
||||
return m_current_type;
|
||||
}
|
||||
|
||||
QString MMCIcon::name() const
|
||||
{
|
||||
if (m_name.size())
|
||||
return m_name;
|
||||
return m_key;
|
||||
}
|
||||
|
||||
bool MMCIcon::has(MMCIcon::Type _type) const
|
||||
{
|
||||
return m_images[_type].present();
|
||||
}
|
||||
|
||||
QIcon MMCIcon::icon() const
|
||||
{
|
||||
if (m_current_type == Type::ToBeDeleted)
|
||||
return QIcon();
|
||||
return m_images[m_current_type].icon;
|
||||
}
|
||||
|
||||
void MMCIcon::remove(Type rm_type)
|
||||
{
|
||||
m_images[rm_type].filename = QString();
|
||||
m_images[rm_type].icon = QIcon();
|
||||
for (auto iter = rm_type; iter != Type::ToBeDeleted; iter--)
|
||||
{
|
||||
if (m_images[iter].present())
|
||||
{
|
||||
m_current_type = iter;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_current_type = Type::ToBeDeleted;
|
||||
}
|
||||
|
||||
void MMCIcon::replace(MMCIcon::Type new_type, QIcon icon, QString path)
|
||||
{
|
||||
QFileInfo foo(path);
|
||||
if (new_type > m_current_type || m_current_type == MMCIcon::ToBeDeleted)
|
||||
{
|
||||
m_current_type = new_type;
|
||||
}
|
||||
m_images[new_type].icon = icon;
|
||||
m_images[new_type].changed = foo.lastModified();
|
||||
m_images[new_type].filename = path;
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/* Copyright 2013-2015 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 <QDateTime>
|
||||
#include <QIcon>
|
||||
|
||||
#include "multimc_gui_export.h"
|
||||
|
||||
struct MULTIMC_GUI_EXPORT MMCImage
|
||||
{
|
||||
QIcon icon;
|
||||
QString filename;
|
||||
QDateTime changed;
|
||||
bool present() const
|
||||
{
|
||||
return !icon.isNull();
|
||||
}
|
||||
};
|
||||
|
||||
struct MULTIMC_GUI_EXPORT MMCIcon
|
||||
{
|
||||
enum Type : unsigned
|
||||
{
|
||||
Builtin,
|
||||
Transient,
|
||||
FileBased,
|
||||
ICONS_TOTAL,
|
||||
ToBeDeleted
|
||||
};
|
||||
QString m_key;
|
||||
QString m_name;
|
||||
MMCImage m_images[ICONS_TOTAL];
|
||||
Type m_current_type = ToBeDeleted;
|
||||
|
||||
Type type() const;
|
||||
QString name() const;
|
||||
bool has(Type _type) const;
|
||||
QIcon icon() const;
|
||||
void remove(Type rm_type);
|
||||
void replace(Type new_type, QIcon icon, QString path = QString());
|
||||
};
|
26
libraries/hoedown/CMakeLists.txt
Normal file
26
libraries/hoedown/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
||||
# hoedown 3.0.2 - https://github.com/hoedown/hoedown/archive/3.0.2.tar.gz
|
||||
project(hoedown LANGUAGES C VERSION 3.0.2)
|
||||
|
||||
set(HOEDOWN_SOURCES
|
||||
include/hoedown/autolink.h
|
||||
include/hoedown/buffer.h
|
||||
include/hoedown/document.h
|
||||
include/hoedown/escape.h
|
||||
include/hoedown/html.h
|
||||
include/hoedown/stack.h
|
||||
include/hoedown/version.h
|
||||
src/autolink.c
|
||||
src/buffer.c
|
||||
src/document.c
|
||||
src/escape.c
|
||||
src/html.c
|
||||
src/html_blocks.c
|
||||
src/html_smartypants.c
|
||||
src/stack.c
|
||||
src/version.c
|
||||
)
|
||||
|
||||
# Include self.
|
||||
add_library(hoedown STATIC ${HOEDOWN_SOURCES})
|
||||
|
||||
target_include_directories(hoedown PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
15
libraries/hoedown/LICENSE
Normal file
15
libraries/hoedown/LICENSE
Normal file
@ -0,0 +1,15 @@
|
||||
Copyright (c) 2008, Natacha Porté
|
||||
Copyright (c) 2011, Vicent Martí
|
||||
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
9
libraries/hoedown/README.md
Normal file
9
libraries/hoedown/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
Hoedown
|
||||
=======
|
||||
|
||||
This is Hoedown 3.0.2, taken from [the hoedown github repo](https://github.com/hoedown/hoedown).
|
||||
|
||||
`Hoedown` is a revived fork of [Sundown](https://github.com/vmg/sundown),
|
||||
the Markdown parser based on the original code of the
|
||||
[Upskirt library](http://fossil.instinctive.eu/libupskirt/index)
|
||||
by Natacha Porté.
|
46
libraries/hoedown/include/hoedown/autolink.h
Normal file
46
libraries/hoedown/include/hoedown/autolink.h
Normal file
@ -0,0 +1,46 @@
|
||||
/* autolink.h - versatile autolinker */
|
||||
|
||||
#ifndef HOEDOWN_AUTOLINK_H
|
||||
#define HOEDOWN_AUTOLINK_H
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/*************
|
||||
* CONSTANTS *
|
||||
*************/
|
||||
|
||||
typedef enum hoedown_autolink_flags {
|
||||
HOEDOWN_AUTOLINK_SHORT_DOMAINS = (1 << 0)
|
||||
} hoedown_autolink_flags;
|
||||
|
||||
|
||||
/*************
|
||||
* FUNCTIONS *
|
||||
*************/
|
||||
|
||||
/* hoedown_autolink_is_safe: verify that a URL has a safe protocol */
|
||||
int hoedown_autolink_is_safe(const uint8_t *data, size_t size);
|
||||
|
||||
/* hoedown_autolink__www: search for the next www link in data */
|
||||
size_t hoedown_autolink__www(size_t *rewind_p, hoedown_buffer *link,
|
||||
uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
|
||||
|
||||
/* hoedown_autolink__email: search for the next email in data */
|
||||
size_t hoedown_autolink__email(size_t *rewind_p, hoedown_buffer *link,
|
||||
uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
|
||||
|
||||
/* hoedown_autolink__url: search for the next URL in data */
|
||||
size_t hoedown_autolink__url(size_t *rewind_p, hoedown_buffer *link,
|
||||
uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /** HOEDOWN_AUTOLINK_H **/
|
134
libraries/hoedown/include/hoedown/buffer.h
Normal file
134
libraries/hoedown/include/hoedown/buffer.h
Normal file
@ -0,0 +1,134 @@
|
||||
/* buffer.h - simple, fast buffers */
|
||||
|
||||
#ifndef HOEDOWN_BUFFER_H
|
||||
#define HOEDOWN_BUFFER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define __attribute__(x)
|
||||
#define inline __inline
|
||||
#define __builtin_expect(x,n) x
|
||||
#endif
|
||||
|
||||
|
||||
/*********
|
||||
* TYPES *
|
||||
*********/
|
||||
|
||||
typedef void *(*hoedown_realloc_callback)(void *, size_t);
|
||||
typedef void (*hoedown_free_callback)(void *);
|
||||
|
||||
struct hoedown_buffer {
|
||||
uint8_t *data; /* actual character data */
|
||||
size_t size; /* size of the string */
|
||||
size_t asize; /* allocated size (0 = volatile buffer) */
|
||||
size_t unit; /* reallocation unit size (0 = read-only buffer) */
|
||||
|
||||
hoedown_realloc_callback data_realloc;
|
||||
hoedown_free_callback data_free;
|
||||
hoedown_free_callback buffer_free;
|
||||
};
|
||||
|
||||
typedef struct hoedown_buffer hoedown_buffer;
|
||||
|
||||
|
||||
/*************
|
||||
* FUNCTIONS *
|
||||
*************/
|
||||
|
||||
/* allocation wrappers */
|
||||
void *hoedown_malloc(size_t size) __attribute__ ((malloc));
|
||||
void *hoedown_calloc(size_t nmemb, size_t size) __attribute__ ((malloc));
|
||||
void *hoedown_realloc(void *ptr, size_t size) __attribute__ ((malloc));
|
||||
|
||||
/* hoedown_buffer_init: initialize a buffer with custom allocators */
|
||||
void hoedown_buffer_init(
|
||||
hoedown_buffer *buffer,
|
||||
size_t unit,
|
||||
hoedown_realloc_callback data_realloc,
|
||||
hoedown_free_callback data_free,
|
||||
hoedown_free_callback buffer_free
|
||||
);
|
||||
|
||||
/* hoedown_buffer_uninit: uninitialize an existing buffer */
|
||||
void hoedown_buffer_uninit(hoedown_buffer *buf);
|
||||
|
||||
/* hoedown_buffer_new: allocate a new buffer */
|
||||
hoedown_buffer *hoedown_buffer_new(size_t unit) __attribute__ ((malloc));
|
||||
|
||||
/* hoedown_buffer_reset: free internal data of the buffer */
|
||||
void hoedown_buffer_reset(hoedown_buffer *buf);
|
||||
|
||||
/* hoedown_buffer_grow: increase the allocated size to the given value */
|
||||
void hoedown_buffer_grow(hoedown_buffer *buf, size_t neosz);
|
||||
|
||||
/* hoedown_buffer_put: append raw data to a buffer */
|
||||
void hoedown_buffer_put(hoedown_buffer *buf, const uint8_t *data, size_t size);
|
||||
|
||||
/* hoedown_buffer_puts: append a NUL-terminated string to a buffer */
|
||||
void hoedown_buffer_puts(hoedown_buffer *buf, const char *str);
|
||||
|
||||
/* hoedown_buffer_putc: append a single char to a buffer */
|
||||
void hoedown_buffer_putc(hoedown_buffer *buf, uint8_t c);
|
||||
|
||||
/* hoedown_buffer_putf: read from a file and append to a buffer, until EOF or error */
|
||||
int hoedown_buffer_putf(hoedown_buffer *buf, FILE* file);
|
||||
|
||||
/* hoedown_buffer_set: replace the buffer's contents with raw data */
|
||||
void hoedown_buffer_set(hoedown_buffer *buf, const uint8_t *data, size_t size);
|
||||
|
||||
/* hoedown_buffer_sets: replace the buffer's contents with a NUL-terminated string */
|
||||
void hoedown_buffer_sets(hoedown_buffer *buf, const char *str);
|
||||
|
||||
/* hoedown_buffer_eq: compare a buffer's data with other data for equality */
|
||||
int hoedown_buffer_eq(const hoedown_buffer *buf, const uint8_t *data, size_t size);
|
||||
|
||||
/* hoedown_buffer_eq: compare a buffer's data with NUL-terminated string for equality */
|
||||
int hoedown_buffer_eqs(const hoedown_buffer *buf, const char *str);
|
||||
|
||||
/* hoedown_buffer_prefix: compare the beginning of a buffer with a string */
|
||||
int hoedown_buffer_prefix(const hoedown_buffer *buf, const char *prefix);
|
||||
|
||||
/* hoedown_buffer_slurp: remove a given number of bytes from the head of the buffer */
|
||||
void hoedown_buffer_slurp(hoedown_buffer *buf, size_t size);
|
||||
|
||||
/* hoedown_buffer_cstr: NUL-termination of the string array (making a C-string) */
|
||||
const char *hoedown_buffer_cstr(hoedown_buffer *buf);
|
||||
|
||||
/* hoedown_buffer_printf: formatted printing to a buffer */
|
||||
void hoedown_buffer_printf(hoedown_buffer *buf, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
|
||||
/* hoedown_buffer_put_utf8: put a Unicode character encoded as UTF-8 */
|
||||
void hoedown_buffer_put_utf8(hoedown_buffer *buf, unsigned int codepoint);
|
||||
|
||||
/* hoedown_buffer_free: free the buffer */
|
||||
void hoedown_buffer_free(hoedown_buffer *buf);
|
||||
|
||||
|
||||
/* HOEDOWN_BUFPUTSL: optimized hoedown_buffer_puts of a string literal */
|
||||
#define HOEDOWN_BUFPUTSL(output, literal) \
|
||||
hoedown_buffer_put(output, (const uint8_t *)literal, sizeof(literal) - 1)
|
||||
|
||||
/* HOEDOWN_BUFSETSL: optimized hoedown_buffer_sets of a string literal */
|
||||
#define HOEDOWN_BUFSETSL(output, literal) \
|
||||
hoedown_buffer_set(output, (const uint8_t *)literal, sizeof(literal) - 1)
|
||||
|
||||
/* HOEDOWN_BUFEQSL: optimized hoedown_buffer_eqs of a string literal */
|
||||
#define HOEDOWN_BUFEQSL(output, literal) \
|
||||
hoedown_buffer_eq(output, (const uint8_t *)literal, sizeof(literal) - 1)
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /** HOEDOWN_BUFFER_H **/
|
172
libraries/hoedown/include/hoedown/document.h
Normal file
172
libraries/hoedown/include/hoedown/document.h
Normal file
@ -0,0 +1,172 @@
|
||||
/* document.h - generic markdown parser */
|
||||
|
||||
#ifndef HOEDOWN_DOCUMENT_H
|
||||
#define HOEDOWN_DOCUMENT_H
|
||||
|
||||
#include "buffer.h"
|
||||
#include "autolink.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/*************
|
||||
* CONSTANTS *
|
||||
*************/
|
||||
|
||||
typedef enum hoedown_extensions {
|
||||
/* block-level extensions */
|
||||
HOEDOWN_EXT_TABLES = (1 << 0),
|
||||
HOEDOWN_EXT_FENCED_CODE = (1 << 1),
|
||||
HOEDOWN_EXT_FOOTNOTES = (1 << 2),
|
||||
|
||||
/* span-level extensions */
|
||||
HOEDOWN_EXT_AUTOLINK = (1 << 3),
|
||||
HOEDOWN_EXT_STRIKETHROUGH = (1 << 4),
|
||||
HOEDOWN_EXT_UNDERLINE = (1 << 5),
|
||||
HOEDOWN_EXT_HIGHLIGHT = (1 << 6),
|
||||
HOEDOWN_EXT_QUOTE = (1 << 7),
|
||||
HOEDOWN_EXT_SUPERSCRIPT = (1 << 8),
|
||||
HOEDOWN_EXT_MATH = (1 << 9),
|
||||
|
||||
/* other flags */
|
||||
HOEDOWN_EXT_NO_INTRA_EMPHASIS = (1 << 11),
|
||||
HOEDOWN_EXT_SPACE_HEADERS = (1 << 12),
|
||||
HOEDOWN_EXT_MATH_EXPLICIT = (1 << 13),
|
||||
|
||||
/* negative flags */
|
||||
HOEDOWN_EXT_DISABLE_INDENTED_CODE = (1 << 14)
|
||||
} hoedown_extensions;
|
||||
|
||||
#define HOEDOWN_EXT_BLOCK (\
|
||||
HOEDOWN_EXT_TABLES |\
|
||||
HOEDOWN_EXT_FENCED_CODE |\
|
||||
HOEDOWN_EXT_FOOTNOTES )
|
||||
|
||||
#define HOEDOWN_EXT_SPAN (\
|
||||
HOEDOWN_EXT_AUTOLINK |\
|
||||
HOEDOWN_EXT_STRIKETHROUGH |\
|
||||
HOEDOWN_EXT_UNDERLINE |\
|
||||
HOEDOWN_EXT_HIGHLIGHT |\
|
||||
HOEDOWN_EXT_QUOTE |\
|
||||
HOEDOWN_EXT_SUPERSCRIPT |\
|
||||
HOEDOWN_EXT_MATH )
|
||||
|
||||
#define HOEDOWN_EXT_FLAGS (\
|
||||
HOEDOWN_EXT_NO_INTRA_EMPHASIS |\
|
||||
HOEDOWN_EXT_SPACE_HEADERS |\
|
||||
HOEDOWN_EXT_MATH_EXPLICIT )
|
||||
|
||||
#define HOEDOWN_EXT_NEGATIVE (\
|
||||
HOEDOWN_EXT_DISABLE_INDENTED_CODE )
|
||||
|
||||
typedef enum hoedown_list_flags {
|
||||
HOEDOWN_LIST_ORDERED = (1 << 0),
|
||||
HOEDOWN_LI_BLOCK = (1 << 1) /* <li> containing block data */
|
||||
} hoedown_list_flags;
|
||||
|
||||
typedef enum hoedown_table_flags {
|
||||
HOEDOWN_TABLE_ALIGN_LEFT = 1,
|
||||
HOEDOWN_TABLE_ALIGN_RIGHT = 2,
|
||||
HOEDOWN_TABLE_ALIGN_CENTER = 3,
|
||||
HOEDOWN_TABLE_ALIGNMASK = 3,
|
||||
HOEDOWN_TABLE_HEADER = 4
|
||||
} hoedown_table_flags;
|
||||
|
||||
typedef enum hoedown_autolink_type {
|
||||
HOEDOWN_AUTOLINK_NONE, /* used internally when it is not an autolink*/
|
||||
HOEDOWN_AUTOLINK_NORMAL, /* normal http/http/ftp/mailto/etc link */
|
||||
HOEDOWN_AUTOLINK_EMAIL /* e-mail link without explit mailto: */
|
||||
} hoedown_autolink_type;
|
||||
|
||||
|
||||
/*********
|
||||
* TYPES *
|
||||
*********/
|
||||
|
||||
struct hoedown_document;
|
||||
typedef struct hoedown_document hoedown_document;
|
||||
|
||||
struct hoedown_renderer_data {
|
||||
void *opaque;
|
||||
};
|
||||
typedef struct hoedown_renderer_data hoedown_renderer_data;
|
||||
|
||||
/* hoedown_renderer - functions for rendering parsed data */
|
||||
struct hoedown_renderer {
|
||||
/* state object */
|
||||
void *opaque;
|
||||
|
||||
/* block level callbacks - NULL skips the block */
|
||||
void (*blockcode)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data);
|
||||
void (*blockquote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
void (*header)(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data);
|
||||
void (*hrule)(hoedown_buffer *ob, const hoedown_renderer_data *data);
|
||||
void (*list)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data);
|
||||
void (*listitem)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data);
|
||||
void (*paragraph)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
void (*table)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
void (*table_header)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
void (*table_body)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
void (*table_row)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
void (*table_cell)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data);
|
||||
void (*footnotes)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
void (*footnote_def)(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data);
|
||||
void (*blockhtml)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||
|
||||
/* span level callbacks - NULL or return 0 prints the span verbatim */
|
||||
int (*autolink)(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data);
|
||||
int (*codespan)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||
int (*double_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
int (*emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
int (*underline)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
int (*highlight)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
int (*quote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
int (*image)(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data);
|
||||
int (*linebreak)(hoedown_buffer *ob, const hoedown_renderer_data *data);
|
||||
int (*link)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data);
|
||||
int (*triple_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
int (*strikethrough)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
int (*superscript)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||
int (*footnote_ref)(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data);
|
||||
int (*math)(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data);
|
||||
int (*raw_html)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||
|
||||
/* low level callbacks - NULL copies input directly into the output */
|
||||
void (*entity)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||
void (*normal_text)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||
|
||||
/* miscellaneous callbacks */
|
||||
void (*doc_header)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data);
|
||||
void (*doc_footer)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data);
|
||||
};
|
||||
typedef struct hoedown_renderer hoedown_renderer;
|
||||
|
||||
|
||||
/*************
|
||||
* FUNCTIONS *
|
||||
*************/
|
||||
|
||||
/* hoedown_document_new: allocate a new document processor instance */
|
||||
hoedown_document *hoedown_document_new(
|
||||
const hoedown_renderer *renderer,
|
||||
hoedown_extensions extensions,
|
||||
size_t max_nesting
|
||||
) __attribute__ ((malloc));
|
||||
|
||||
/* hoedown_document_render: render regular Markdown using the document processor */
|
||||
void hoedown_document_render(hoedown_document *doc, hoedown_buffer *ob, const uint8_t *data, size_t size);
|
||||
|
||||
/* hoedown_document_render_inline: render inline Markdown using the document processor */
|
||||
void hoedown_document_render_inline(hoedown_document *doc, hoedown_buffer *ob, const uint8_t *data, size_t size);
|
||||
|
||||
/* hoedown_document_free: deallocate a document processor instance */
|
||||
void hoedown_document_free(hoedown_document *doc);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /** HOEDOWN_DOCUMENT_H **/
|
28
libraries/hoedown/include/hoedown/escape.h
Normal file
28
libraries/hoedown/include/hoedown/escape.h
Normal file
@ -0,0 +1,28 @@
|
||||
/* escape.h - escape utilities */
|
||||
|
||||
#ifndef HOEDOWN_ESCAPE_H
|
||||
#define HOEDOWN_ESCAPE_H
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/*************
|
||||
* FUNCTIONS *
|
||||
*************/
|
||||
|
||||
/* hoedown_escape_href: escape (part of) a URL inside HTML */
|
||||
void hoedown_escape_href(hoedown_buffer *ob, const uint8_t *data, size_t size);
|
||||
|
||||
/* hoedown_escape_html: escape HTML */
|
||||
void hoedown_escape_html(hoedown_buffer *ob, const uint8_t *data, size_t size, int secure);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /** HOEDOWN_ESCAPE_H **/
|
84
libraries/hoedown/include/hoedown/html.h
Normal file
84
libraries/hoedown/include/hoedown/html.h
Normal file
@ -0,0 +1,84 @@
|
||||
/* html.h - HTML renderer and utilities */
|
||||
|
||||
#ifndef HOEDOWN_HTML_H
|
||||
#define HOEDOWN_HTML_H
|
||||
|
||||
#include "document.h"
|
||||
#include "buffer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/*************
|
||||
* CONSTANTS *
|
||||
*************/
|
||||
|
||||
typedef enum hoedown_html_flags {
|
||||
HOEDOWN_HTML_SKIP_HTML = (1 << 0),
|
||||
HOEDOWN_HTML_ESCAPE = (1 << 1),
|
||||
HOEDOWN_HTML_HARD_WRAP = (1 << 2),
|
||||
HOEDOWN_HTML_USE_XHTML = (1 << 3)
|
||||
} hoedown_html_flags;
|
||||
|
||||
typedef enum hoedown_html_tag {
|
||||
HOEDOWN_HTML_TAG_NONE = 0,
|
||||
HOEDOWN_HTML_TAG_OPEN,
|
||||
HOEDOWN_HTML_TAG_CLOSE
|
||||
} hoedown_html_tag;
|
||||
|
||||
|
||||
/*********
|
||||
* TYPES *
|
||||
*********/
|
||||
|
||||
struct hoedown_html_renderer_state {
|
||||
void *opaque;
|
||||
|
||||
struct {
|
||||
int header_count;
|
||||
int current_level;
|
||||
int level_offset;
|
||||
int nesting_level;
|
||||
} toc_data;
|
||||
|
||||
hoedown_html_flags flags;
|
||||
|
||||
/* extra callbacks */
|
||||
void (*link_attributes)(hoedown_buffer *ob, const hoedown_buffer *url, const hoedown_renderer_data *data);
|
||||
};
|
||||
typedef struct hoedown_html_renderer_state hoedown_html_renderer_state;
|
||||
|
||||
|
||||
/*************
|
||||
* FUNCTIONS *
|
||||
*************/
|
||||
|
||||
/* hoedown_html_smartypants: process an HTML snippet using SmartyPants for smart punctuation */
|
||||
void hoedown_html_smartypants(hoedown_buffer *ob, const uint8_t *data, size_t size);
|
||||
|
||||
/* hoedown_html_is_tag: checks if data starts with a specific tag, returns the tag type or NONE */
|
||||
hoedown_html_tag hoedown_html_is_tag(const uint8_t *data, size_t size, const char *tagname);
|
||||
|
||||
|
||||
/* hoedown_html_renderer_new: allocates a regular HTML renderer */
|
||||
hoedown_renderer *hoedown_html_renderer_new(
|
||||
hoedown_html_flags render_flags,
|
||||
int nesting_level
|
||||
) __attribute__ ((malloc));
|
||||
|
||||
/* hoedown_html_toc_renderer_new: like hoedown_html_renderer_new, but the returned renderer produces the Table of Contents */
|
||||
hoedown_renderer *hoedown_html_toc_renderer_new(
|
||||
int nesting_level
|
||||
) __attribute__ ((malloc));
|
||||
|
||||
/* hoedown_html_renderer_free: deallocate an HTML renderer */
|
||||
void hoedown_html_renderer_free(hoedown_renderer *renderer);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /** HOEDOWN_HTML_H **/
|
52
libraries/hoedown/include/hoedown/stack.h
Normal file
52
libraries/hoedown/include/hoedown/stack.h
Normal file
@ -0,0 +1,52 @@
|
||||
/* stack.h - simple stacking */
|
||||
|
||||
#ifndef HOEDOWN_STACK_H
|
||||
#define HOEDOWN_STACK_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/*********
|
||||
* TYPES *
|
||||
*********/
|
||||
|
||||
struct hoedown_stack {
|
||||
void **item;
|
||||
size_t size;
|
||||
size_t asize;
|
||||
};
|
||||
typedef struct hoedown_stack hoedown_stack;
|
||||
|
||||
|
||||
/*************
|
||||
* FUNCTIONS *
|
||||
*************/
|
||||
|
||||
/* hoedown_stack_init: initialize a stack */
|
||||
void hoedown_stack_init(hoedown_stack *st, size_t initial_size);
|
||||
|
||||
/* hoedown_stack_uninit: free internal data of the stack */
|
||||
void hoedown_stack_uninit(hoedown_stack *st);
|
||||
|
||||
/* hoedown_stack_grow: increase the allocated size to the given value */
|
||||
void hoedown_stack_grow(hoedown_stack *st, size_t neosz);
|
||||
|
||||
/* hoedown_stack_push: push an item to the top of the stack */
|
||||
void hoedown_stack_push(hoedown_stack *st, void *item);
|
||||
|
||||
/* hoedown_stack_pop: retrieve and remove the item at the top of the stack */
|
||||
void *hoedown_stack_pop(hoedown_stack *st);
|
||||
|
||||
/* hoedown_stack_top: retrieve the item at the top of the stack */
|
||||
void *hoedown_stack_top(const hoedown_stack *st);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /** HOEDOWN_STACK_H **/
|
33
libraries/hoedown/include/hoedown/version.h
Normal file
33
libraries/hoedown/include/hoedown/version.h
Normal file
@ -0,0 +1,33 @@
|
||||
/* version.h - holds Hoedown's version */
|
||||
|
||||
#ifndef HOEDOWN_VERSION_H
|
||||
#define HOEDOWN_VERSION_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/*************
|
||||
* CONSTANTS *
|
||||
*************/
|
||||
|
||||
#define HOEDOWN_VERSION "3.0.2"
|
||||
#define HOEDOWN_VERSION_MAJOR 3
|
||||
#define HOEDOWN_VERSION_MINOR 0
|
||||
#define HOEDOWN_VERSION_REVISION 2
|
||||
|
||||
|
||||
/*************
|
||||
* FUNCTIONS *
|
||||
*************/
|
||||
|
||||
/* hoedown_version: retrieve Hoedown's version numbers */
|
||||
void hoedown_version(int *major, int *minor, int *revision);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /** HOEDOWN_VERSION_H **/
|
281
libraries/hoedown/src/autolink.c
Normal file
281
libraries/hoedown/src/autolink.c
Normal file
@ -0,0 +1,281 @@
|
||||
#include "hoedown/autolink.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <strings.h>
|
||||
#else
|
||||
#define strncasecmp _strnicmp
|
||||
#endif
|
||||
|
||||
int
|
||||
hoedown_autolink_is_safe(const uint8_t *data, size_t size)
|
||||
{
|
||||
static const size_t valid_uris_count = 6;
|
||||
static const char *valid_uris[] = {
|
||||
"http://", "https://", "/", "#", "ftp://", "mailto:"
|
||||
};
|
||||
static const size_t valid_uris_size[] = { 7, 8, 1, 1, 6, 7 };
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < valid_uris_count; ++i) {
|
||||
size_t len = valid_uris_size[i];
|
||||
|
||||
if (size > len &&
|
||||
strncasecmp((char *)data, valid_uris[i], len) == 0 &&
|
||||
isalnum(data[len]))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
autolink_delim(uint8_t *data, size_t link_end, size_t max_rewind, size_t size)
|
||||
{
|
||||
uint8_t cclose, copen = 0;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < link_end; ++i)
|
||||
if (data[i] == '<') {
|
||||
link_end = i;
|
||||
break;
|
||||
}
|
||||
|
||||
while (link_end > 0) {
|
||||
if (strchr("?!.,:", data[link_end - 1]) != NULL)
|
||||
link_end--;
|
||||
|
||||
else if (data[link_end - 1] == ';') {
|
||||
size_t new_end = link_end - 2;
|
||||
|
||||
while (new_end > 0 && isalpha(data[new_end]))
|
||||
new_end--;
|
||||
|
||||
if (new_end < link_end - 2 && data[new_end] == '&')
|
||||
link_end = new_end;
|
||||
else
|
||||
link_end--;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
if (link_end == 0)
|
||||
return 0;
|
||||
|
||||
cclose = data[link_end - 1];
|
||||
|
||||
switch (cclose) {
|
||||
case '"': copen = '"'; break;
|
||||
case '\'': copen = '\''; break;
|
||||
case ')': copen = '('; break;
|
||||
case ']': copen = '['; break;
|
||||
case '}': copen = '{'; break;
|
||||
}
|
||||
|
||||
if (copen != 0) {
|
||||
size_t closing = 0;
|
||||
size_t opening = 0;
|
||||
size_t i = 0;
|
||||
|
||||
/* Try to close the final punctuation sign in this same line;
|
||||
* if we managed to close it outside of the URL, that means that it's
|
||||
* not part of the URL. If it closes inside the URL, that means it
|
||||
* is part of the URL.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* foo http://www.pokemon.com/Pikachu_(Electric) bar
|
||||
* => http://www.pokemon.com/Pikachu_(Electric)
|
||||
*
|
||||
* foo (http://www.pokemon.com/Pikachu_(Electric)) bar
|
||||
* => http://www.pokemon.com/Pikachu_(Electric)
|
||||
*
|
||||
* foo http://www.pokemon.com/Pikachu_(Electric)) bar
|
||||
* => http://www.pokemon.com/Pikachu_(Electric))
|
||||
*
|
||||
* (foo http://www.pokemon.com/Pikachu_(Electric)) bar
|
||||
* => foo http://www.pokemon.com/Pikachu_(Electric)
|
||||
*/
|
||||
|
||||
while (i < link_end) {
|
||||
if (data[i] == copen)
|
||||
opening++;
|
||||
else if (data[i] == cclose)
|
||||
closing++;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (closing != opening)
|
||||
link_end--;
|
||||
}
|
||||
|
||||
return link_end;
|
||||
}
|
||||
|
||||
static size_t
|
||||
check_domain(uint8_t *data, size_t size, int allow_short)
|
||||
{
|
||||
size_t i, np = 0;
|
||||
|
||||
if (!isalnum(data[0]))
|
||||
return 0;
|
||||
|
||||
for (i = 1; i < size - 1; ++i) {
|
||||
if (strchr(".:", data[i]) != NULL) np++;
|
||||
else if (!isalnum(data[i]) && data[i] != '-') break;
|
||||
}
|
||||
|
||||
if (allow_short) {
|
||||
/* We don't need a valid domain in the strict sense (with
|
||||
* least one dot; so just make sure it's composed of valid
|
||||
* domain characters and return the length of the the valid
|
||||
* sequence. */
|
||||
return i;
|
||||
} else {
|
||||
/* a valid domain needs to have at least a dot.
|
||||
* that's as far as we get */
|
||||
return np ? i : 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
hoedown_autolink__www(
|
||||
size_t *rewind_p,
|
||||
hoedown_buffer *link,
|
||||
uint8_t *data,
|
||||
size_t max_rewind,
|
||||
size_t size,
|
||||
unsigned int flags)
|
||||
{
|
||||
size_t link_end;
|
||||
|
||||
if (max_rewind > 0 && !ispunct(data[-1]) && !isspace(data[-1]))
|
||||
return 0;
|
||||
|
||||
if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
|
||||
return 0;
|
||||
|
||||
link_end = check_domain(data, size, 0);
|
||||
|
||||
if (link_end == 0)
|
||||
return 0;
|
||||
|
||||
while (link_end < size && !isspace(data[link_end]))
|
||||
link_end++;
|
||||
|
||||
link_end = autolink_delim(data, link_end, max_rewind, size);
|
||||
|
||||
if (link_end == 0)
|
||||
return 0;
|
||||
|
||||
hoedown_buffer_put(link, data, link_end);
|
||||
*rewind_p = 0;
|
||||
|
||||
return (int)link_end;
|
||||
}
|
||||
|
||||
size_t
|
||||
hoedown_autolink__email(
|
||||
size_t *rewind_p,
|
||||
hoedown_buffer *link,
|
||||
uint8_t *data,
|
||||
size_t max_rewind,
|
||||
size_t size,
|
||||
unsigned int flags)
|
||||
{
|
||||
size_t link_end, rewind;
|
||||
int nb = 0, np = 0;
|
||||
|
||||
for (rewind = 0; rewind < max_rewind; ++rewind) {
|
||||
uint8_t c = data[-1 - rewind];
|
||||
|
||||
if (isalnum(c))
|
||||
continue;
|
||||
|
||||
if (strchr(".+-_", c) != NULL)
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (rewind == 0)
|
||||
return 0;
|
||||
|
||||
for (link_end = 0; link_end < size; ++link_end) {
|
||||
uint8_t c = data[link_end];
|
||||
|
||||
if (isalnum(c))
|
||||
continue;
|
||||
|
||||
if (c == '@')
|
||||
nb++;
|
||||
else if (c == '.' && link_end < size - 1)
|
||||
np++;
|
||||
else if (c != '-' && c != '_')
|
||||
break;
|
||||
}
|
||||
|
||||
if (link_end < 2 || nb != 1 || np == 0 ||
|
||||
!isalpha(data[link_end - 1]))
|
||||
return 0;
|
||||
|
||||
link_end = autolink_delim(data, link_end, max_rewind, size);
|
||||
|
||||
if (link_end == 0)
|
||||
return 0;
|
||||
|
||||
hoedown_buffer_put(link, data - rewind, link_end + rewind);
|
||||
*rewind_p = rewind;
|
||||
|
||||
return link_end;
|
||||
}
|
||||
|
||||
size_t
|
||||
hoedown_autolink__url(
|
||||
size_t *rewind_p,
|
||||
hoedown_buffer *link,
|
||||
uint8_t *data,
|
||||
size_t max_rewind,
|
||||
size_t size,
|
||||
unsigned int flags)
|
||||
{
|
||||
size_t link_end, rewind = 0, domain_len;
|
||||
|
||||
if (size < 4 || data[1] != '/' || data[2] != '/')
|
||||
return 0;
|
||||
|
||||
while (rewind < max_rewind && isalpha(data[-1 - rewind]))
|
||||
rewind++;
|
||||
|
||||
if (!hoedown_autolink_is_safe(data - rewind, size + rewind))
|
||||
return 0;
|
||||
|
||||
link_end = strlen("://");
|
||||
|
||||
domain_len = check_domain(
|
||||
data + link_end,
|
||||
size - link_end,
|
||||
flags & HOEDOWN_AUTOLINK_SHORT_DOMAINS);
|
||||
|
||||
if (domain_len == 0)
|
||||
return 0;
|
||||
|
||||
link_end += domain_len;
|
||||
while (link_end < size && !isspace(data[link_end]))
|
||||
link_end++;
|
||||
|
||||
link_end = autolink_delim(data, link_end, max_rewind, size);
|
||||
|
||||
if (link_end == 0)
|
||||
return 0;
|
||||
|
||||
hoedown_buffer_put(link, data - rewind, link_end + rewind);
|
||||
*rewind_p = rewind;
|
||||
|
||||
return link_end;
|
||||
}
|
308
libraries/hoedown/src/buffer.c
Normal file
308
libraries/hoedown/src/buffer.c
Normal file
@ -0,0 +1,308 @@
|
||||
#include "hoedown/buffer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
void *
|
||||
hoedown_malloc(size_t size)
|
||||
{
|
||||
void *ret = malloc(size);
|
||||
|
||||
if (!ret) {
|
||||
fprintf(stderr, "Allocation failed.\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *
|
||||
hoedown_calloc(size_t nmemb, size_t size)
|
||||
{
|
||||
void *ret = calloc(nmemb, size);
|
||||
|
||||
if (!ret) {
|
||||
fprintf(stderr, "Allocation failed.\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *
|
||||
hoedown_realloc(void *ptr, size_t size)
|
||||
{
|
||||
void *ret = realloc(ptr, size);
|
||||
|
||||
if (!ret) {
|
||||
fprintf(stderr, "Allocation failed.\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_init(
|
||||
hoedown_buffer *buf,
|
||||
size_t unit,
|
||||
hoedown_realloc_callback data_realloc,
|
||||
hoedown_free_callback data_free,
|
||||
hoedown_free_callback buffer_free)
|
||||
{
|
||||
assert(buf);
|
||||
|
||||
buf->data = NULL;
|
||||
buf->size = buf->asize = 0;
|
||||
buf->unit = unit;
|
||||
buf->data_realloc = data_realloc;
|
||||
buf->data_free = data_free;
|
||||
buf->buffer_free = buffer_free;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_uninit(hoedown_buffer *buf)
|
||||
{
|
||||
assert(buf && buf->unit);
|
||||
buf->data_free(buf->data);
|
||||
}
|
||||
|
||||
hoedown_buffer *
|
||||
hoedown_buffer_new(size_t unit)
|
||||
{
|
||||
hoedown_buffer *ret = hoedown_malloc(sizeof (hoedown_buffer));
|
||||
hoedown_buffer_init(ret, unit, hoedown_realloc, free, free);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_free(hoedown_buffer *buf)
|
||||
{
|
||||
if (!buf) return;
|
||||
assert(buf && buf->unit);
|
||||
|
||||
buf->data_free(buf->data);
|
||||
|
||||
if (buf->buffer_free)
|
||||
buf->buffer_free(buf);
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_reset(hoedown_buffer *buf)
|
||||
{
|
||||
assert(buf && buf->unit);
|
||||
|
||||
buf->data_free(buf->data);
|
||||
buf->data = NULL;
|
||||
buf->size = buf->asize = 0;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_grow(hoedown_buffer *buf, size_t neosz)
|
||||
{
|
||||
size_t neoasz;
|
||||
assert(buf && buf->unit);
|
||||
|
||||
if (buf->asize >= neosz)
|
||||
return;
|
||||
|
||||
neoasz = buf->asize + buf->unit;
|
||||
while (neoasz < neosz)
|
||||
neoasz += buf->unit;
|
||||
|
||||
buf->data = (uint8_t *) buf->data_realloc(buf->data, neoasz);
|
||||
buf->asize = neoasz;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_put(hoedown_buffer *buf, const uint8_t *data, size_t size)
|
||||
{
|
||||
assert(buf && buf->unit);
|
||||
|
||||
if (buf->size + size > buf->asize)
|
||||
hoedown_buffer_grow(buf, buf->size + size);
|
||||
|
||||
memcpy(buf->data + buf->size, data, size);
|
||||
buf->size += size;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_puts(hoedown_buffer *buf, const char *str)
|
||||
{
|
||||
hoedown_buffer_put(buf, (const uint8_t *)str, strlen(str));
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_putc(hoedown_buffer *buf, uint8_t c)
|
||||
{
|
||||
assert(buf && buf->unit);
|
||||
|
||||
if (buf->size >= buf->asize)
|
||||
hoedown_buffer_grow(buf, buf->size + 1);
|
||||
|
||||
buf->data[buf->size] = c;
|
||||
buf->size += 1;
|
||||
}
|
||||
|
||||
int
|
||||
hoedown_buffer_putf(hoedown_buffer *buf, FILE *file)
|
||||
{
|
||||
assert(buf && buf->unit);
|
||||
|
||||
while (!(feof(file) || ferror(file))) {
|
||||
hoedown_buffer_grow(buf, buf->size + buf->unit);
|
||||
buf->size += fread(buf->data + buf->size, 1, buf->unit, file);
|
||||
}
|
||||
|
||||
return ferror(file);
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_set(hoedown_buffer *buf, const uint8_t *data, size_t size)
|
||||
{
|
||||
assert(buf && buf->unit);
|
||||
|
||||
if (size > buf->asize)
|
||||
hoedown_buffer_grow(buf, size);
|
||||
|
||||
memcpy(buf->data, data, size);
|
||||
buf->size = size;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_sets(hoedown_buffer *buf, const char *str)
|
||||
{
|
||||
hoedown_buffer_set(buf, (const uint8_t *)str, strlen(str));
|
||||
}
|
||||
|
||||
int
|
||||
hoedown_buffer_eq(const hoedown_buffer *buf, const uint8_t *data, size_t size)
|
||||
{
|
||||
if (buf->size != size) return 0;
|
||||
return memcmp(buf->data, data, size) == 0;
|
||||
}
|
||||
|
||||
int
|
||||
hoedown_buffer_eqs(const hoedown_buffer *buf, const char *str)
|
||||
{
|
||||
return hoedown_buffer_eq(buf, (const uint8_t *)str, strlen(str));
|
||||
}
|
||||
|
||||
int
|
||||
hoedown_buffer_prefix(const hoedown_buffer *buf, const char *prefix)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < buf->size; ++i) {
|
||||
if (prefix[i] == 0)
|
||||
return 0;
|
||||
|
||||
if (buf->data[i] != prefix[i])
|
||||
return buf->data[i] - prefix[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_slurp(hoedown_buffer *buf, size_t size)
|
||||
{
|
||||
assert(buf && buf->unit);
|
||||
|
||||
if (size >= buf->size) {
|
||||
buf->size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
buf->size -= size;
|
||||
memmove(buf->data, buf->data + size, buf->size);
|
||||
}
|
||||
|
||||
const char *
|
||||
hoedown_buffer_cstr(hoedown_buffer *buf)
|
||||
{
|
||||
assert(buf && buf->unit);
|
||||
|
||||
if (buf->size < buf->asize && buf->data[buf->size] == 0)
|
||||
return (char *)buf->data;
|
||||
|
||||
hoedown_buffer_grow(buf, buf->size + 1);
|
||||
buf->data[buf->size] = 0;
|
||||
|
||||
return (char *)buf->data;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_buffer_printf(hoedown_buffer *buf, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int n;
|
||||
|
||||
assert(buf && buf->unit);
|
||||
|
||||
if (buf->size >= buf->asize)
|
||||
hoedown_buffer_grow(buf, buf->size + 1);
|
||||
|
||||
va_start(ap, fmt);
|
||||
n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (n < 0) {
|
||||
#ifndef _MSC_VER
|
||||
return;
|
||||
#else
|
||||
va_start(ap, fmt);
|
||||
n = _vscprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
#endif
|
||||
}
|
||||
|
||||
if ((size_t)n >= buf->asize - buf->size) {
|
||||
hoedown_buffer_grow(buf, buf->size + n + 1);
|
||||
|
||||
va_start(ap, fmt);
|
||||
n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
if (n < 0)
|
||||
return;
|
||||
|
||||
buf->size += n;
|
||||
}
|
||||
|
||||
void hoedown_buffer_put_utf8(hoedown_buffer *buf, unsigned int c) {
|
||||
unsigned char unichar[4];
|
||||
|
||||
assert(buf && buf->unit);
|
||||
|
||||
if (c < 0x80) {
|
||||
hoedown_buffer_putc(buf, c);
|
||||
}
|
||||
else if (c < 0x800) {
|
||||
unichar[0] = 192 + (c / 64);
|
||||
unichar[1] = 128 + (c % 64);
|
||||
hoedown_buffer_put(buf, unichar, 2);
|
||||
}
|
||||
else if (c - 0xd800u < 0x800) {
|
||||
HOEDOWN_BUFPUTSL(buf, "\xef\xbf\xbd");
|
||||
}
|
||||
else if (c < 0x10000) {
|
||||
unichar[0] = 224 + (c / 4096);
|
||||
unichar[1] = 128 + (c / 64) % 64;
|
||||
unichar[2] = 128 + (c % 64);
|
||||
hoedown_buffer_put(buf, unichar, 3);
|
||||
}
|
||||
else if (c < 0x110000) {
|
||||
unichar[0] = 240 + (c / 262144);
|
||||
unichar[1] = 128 + (c / 4096) % 64;
|
||||
unichar[2] = 128 + (c / 64) % 64;
|
||||
unichar[3] = 128 + (c % 64);
|
||||
hoedown_buffer_put(buf, unichar, 4);
|
||||
}
|
||||
else {
|
||||
HOEDOWN_BUFPUTSL(buf, "\xef\xbf\xbd");
|
||||
}
|
||||
}
|
2958
libraries/hoedown/src/document.c
Normal file
2958
libraries/hoedown/src/document.c
Normal file
File diff suppressed because it is too large
Load Diff
188
libraries/hoedown/src/escape.c
Normal file
188
libraries/hoedown/src/escape.c
Normal file
@ -0,0 +1,188 @@
|
||||
#include "hoedown/escape.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
#define likely(x) __builtin_expect((x),1)
|
||||
#define unlikely(x) __builtin_expect((x),0)
|
||||
|
||||
|
||||
/*
|
||||
* The following characters will not be escaped:
|
||||
*
|
||||
* -_.+!*'(),%#@?=;:/,+&$ alphanum
|
||||
*
|
||||
* Note that this character set is the addition of:
|
||||
*
|
||||
* - The characters which are safe to be in an URL
|
||||
* - The characters which are *not* safe to be in
|
||||
* an URL because they are RESERVED characters.
|
||||
*
|
||||
* We assume (lazily) that any RESERVED char that
|
||||
* appears inside an URL is actually meant to
|
||||
* have its native function (i.e. as an URL
|
||||
* component/separator) and hence needs no escaping.
|
||||
*
|
||||
* There are two exceptions: the chacters & (amp)
|
||||
* and ' (single quote) do not appear in the table.
|
||||
* They are meant to appear in the URL as components,
|
||||
* yet they require special HTML-entity escaping
|
||||
* to generate valid HTML markup.
|
||||
*
|
||||
* All other characters will be escaped to %XX.
|
||||
*
|
||||
*/
|
||||
static const uint8_t HREF_SAFE[UINT8_MAX+1] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
void
|
||||
hoedown_escape_href(hoedown_buffer *ob, const uint8_t *data, size_t size)
|
||||
{
|
||||
static const char hex_chars[] = "0123456789ABCDEF";
|
||||
size_t i = 0, mark;
|
||||
char hex_str[3];
|
||||
|
||||
hex_str[0] = '%';
|
||||
|
||||
while (i < size) {
|
||||
mark = i;
|
||||
while (i < size && HREF_SAFE[data[i]]) i++;
|
||||
|
||||
/* Optimization for cases where there's nothing to escape */
|
||||
if (mark == 0 && i >= size) {
|
||||
hoedown_buffer_put(ob, data, size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (likely(i > mark)) {
|
||||
hoedown_buffer_put(ob, data + mark, i - mark);
|
||||
}
|
||||
|
||||
/* escaping */
|
||||
if (i >= size)
|
||||
break;
|
||||
|
||||
switch (data[i]) {
|
||||
/* amp appears all the time in URLs, but needs
|
||||
* HTML-entity escaping to be inside an href */
|
||||
case '&':
|
||||
HOEDOWN_BUFPUTSL(ob, "&");
|
||||
break;
|
||||
|
||||
/* the single quote is a valid URL character
|
||||
* according to the standard; it needs HTML
|
||||
* entity escaping too */
|
||||
case '\'':
|
||||
HOEDOWN_BUFPUTSL(ob, "'");
|
||||
break;
|
||||
|
||||
/* the space can be escaped to %20 or a plus
|
||||
* sign. we're going with the generic escape
|
||||
* for now. the plus thing is more commonly seen
|
||||
* when building GET strings */
|
||||
#if 0
|
||||
case ' ':
|
||||
hoedown_buffer_putc(ob, '+');
|
||||
break;
|
||||
#endif
|
||||
|
||||
/* every other character goes with a %XX escaping */
|
||||
default:
|
||||
hex_str[1] = hex_chars[(data[i] >> 4) & 0xF];
|
||||
hex_str[2] = hex_chars[data[i] & 0xF];
|
||||
hoedown_buffer_put(ob, (uint8_t *)hex_str, 3);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* According to the OWASP rules:
|
||||
*
|
||||
* & --> &
|
||||
* < --> <
|
||||
* > --> >
|
||||
* " --> "
|
||||
* ' --> ' ' is not recommended
|
||||
* / --> / forward slash is included as it helps end an HTML entity
|
||||
*
|
||||
*/
|
||||
static const uint8_t HTML_ESCAPE_TABLE[UINT8_MAX+1] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
static const char *HTML_ESCAPES[] = {
|
||||
"",
|
||||
""",
|
||||
"&",
|
||||
"'",
|
||||
"/",
|
||||
"<",
|
||||
">"
|
||||
};
|
||||
|
||||
void
|
||||
hoedown_escape_html(hoedown_buffer *ob, const uint8_t *data, size_t size, int secure)
|
||||
{
|
||||
size_t i = 0, mark;
|
||||
|
||||
while (1) {
|
||||
mark = i;
|
||||
while (i < size && HTML_ESCAPE_TABLE[data[i]] == 0) i++;
|
||||
|
||||
/* Optimization for cases where there's nothing to escape */
|
||||
if (mark == 0 && i >= size) {
|
||||
hoedown_buffer_put(ob, data, size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (likely(i > mark))
|
||||
hoedown_buffer_put(ob, data + mark, i - mark);
|
||||
|
||||
if (i >= size) break;
|
||||
|
||||
/* The forward slash is only escaped in secure mode */
|
||||
if (!secure && data[i] == '/') {
|
||||
hoedown_buffer_putc(ob, '/');
|
||||
} else {
|
||||
hoedown_buffer_puts(ob, HTML_ESCAPES[HTML_ESCAPE_TABLE[data[i]]]);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
754
libraries/hoedown/src/html.c
Normal file
754
libraries/hoedown/src/html.c
Normal file
@ -0,0 +1,754 @@
|
||||
#include "hoedown/html.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "hoedown/escape.h"
|
||||
|
||||
#define USE_XHTML(opt) (opt->flags & HOEDOWN_HTML_USE_XHTML)
|
||||
|
||||
hoedown_html_tag
|
||||
hoedown_html_is_tag(const uint8_t *data, size_t size, const char *tagname)
|
||||
{
|
||||
size_t i;
|
||||
int closed = 0;
|
||||
|
||||
if (size < 3 || data[0] != '<')
|
||||
return HOEDOWN_HTML_TAG_NONE;
|
||||
|
||||
i = 1;
|
||||
|
||||
if (data[i] == '/') {
|
||||
closed = 1;
|
||||
i++;
|
||||
}
|
||||
|
||||
for (; i < size; ++i, ++tagname) {
|
||||
if (*tagname == 0)
|
||||
break;
|
||||
|
||||
if (data[i] != *tagname)
|
||||
return HOEDOWN_HTML_TAG_NONE;
|
||||
}
|
||||
|
||||
if (i == size)
|
||||
return HOEDOWN_HTML_TAG_NONE;
|
||||
|
||||
if (isspace(data[i]) || data[i] == '>')
|
||||
return closed ? HOEDOWN_HTML_TAG_CLOSE : HOEDOWN_HTML_TAG_OPEN;
|
||||
|
||||
return HOEDOWN_HTML_TAG_NONE;
|
||||
}
|
||||
|
||||
static void escape_html(hoedown_buffer *ob, const uint8_t *source, size_t length)
|
||||
{
|
||||
hoedown_escape_html(ob, source, length, 0);
|
||||
}
|
||||
|
||||
static void escape_href(hoedown_buffer *ob, const uint8_t *source, size_t length)
|
||||
{
|
||||
hoedown_escape_href(ob, source, length);
|
||||
}
|
||||
|
||||
/********************
|
||||
* GENERIC RENDERER *
|
||||
********************/
|
||||
static int
|
||||
rndr_autolink(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
|
||||
if (!link || !link->size)
|
||||
return 0;
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "<a href=\"");
|
||||
if (type == HOEDOWN_AUTOLINK_EMAIL)
|
||||
HOEDOWN_BUFPUTSL(ob, "mailto:");
|
||||
escape_href(ob, link->data, link->size);
|
||||
|
||||
if (state->link_attributes) {
|
||||
hoedown_buffer_putc(ob, '\"');
|
||||
state->link_attributes(ob, link, data);
|
||||
hoedown_buffer_putc(ob, '>');
|
||||
} else {
|
||||
HOEDOWN_BUFPUTSL(ob, "\">");
|
||||
}
|
||||
|
||||
/*
|
||||
* Pretty printing: if we get an email address as
|
||||
* an actual URI, e.g. `mailto:foo@bar.com`, we don't
|
||||
* want to print the `mailto:` prefix
|
||||
*/
|
||||
if (hoedown_buffer_prefix(link, "mailto:") == 0) {
|
||||
escape_html(ob, link->data + 7, link->size - 7);
|
||||
} else {
|
||||
escape_html(ob, link->data, link->size);
|
||||
}
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "</a>");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_blockcode(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||
|
||||
if (lang) {
|
||||
HOEDOWN_BUFPUTSL(ob, "<pre><code class=\"language-");
|
||||
escape_html(ob, lang->data, lang->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "\">");
|
||||
} else {
|
||||
HOEDOWN_BUFPUTSL(ob, "<pre><code>");
|
||||
}
|
||||
|
||||
if (text)
|
||||
escape_html(ob, text->data, text->size);
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "</code></pre>\n");
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_blockquote(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||
HOEDOWN_BUFPUTSL(ob, "<blockquote>\n");
|
||||
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</blockquote>\n");
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_codespan(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)
|
||||
{
|
||||
HOEDOWN_BUFPUTSL(ob, "<code>");
|
||||
if (text) escape_html(ob, text->data, text->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</code>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_strikethrough(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (!content || !content->size)
|
||||
return 0;
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "<del>");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</del>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_double_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (!content || !content->size)
|
||||
return 0;
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "<strong>");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</strong>");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (!content || !content->size) return 0;
|
||||
HOEDOWN_BUFPUTSL(ob, "<em>");
|
||||
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</em>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_underline(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (!content || !content->size)
|
||||
return 0;
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "<u>");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</u>");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_highlight(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (!content || !content->size)
|
||||
return 0;
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "<mark>");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</mark>");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_quote(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (!content || !content->size)
|
||||
return 0;
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "<q>");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</q>");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_linebreak(hoedown_buffer *ob, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
hoedown_buffer_puts(ob, USE_XHTML(state) ? "<br/>\n" : "<br>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_header(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
|
||||
if (ob->size)
|
||||
hoedown_buffer_putc(ob, '\n');
|
||||
|
||||
if (level <= state->toc_data.nesting_level)
|
||||
hoedown_buffer_printf(ob, "<h%d id=\"toc_%d\">", level, state->toc_data.header_count++);
|
||||
else
|
||||
hoedown_buffer_printf(ob, "<h%d>", level);
|
||||
|
||||
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||
hoedown_buffer_printf(ob, "</h%d>\n", level);
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_link(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "<a href=\"");
|
||||
|
||||
if (link && link->size)
|
||||
escape_href(ob, link->data, link->size);
|
||||
|
||||
if (title && title->size) {
|
||||
HOEDOWN_BUFPUTSL(ob, "\" title=\"");
|
||||
escape_html(ob, title->data, title->size);
|
||||
}
|
||||
|
||||
if (state->link_attributes) {
|
||||
hoedown_buffer_putc(ob, '\"');
|
||||
state->link_attributes(ob, link, data);
|
||||
hoedown_buffer_putc(ob, '>');
|
||||
} else {
|
||||
HOEDOWN_BUFPUTSL(ob, "\">");
|
||||
}
|
||||
|
||||
if (content && content->size) hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</a>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_list(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||
hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "<ol>\n" : "<ul>\n"), 5);
|
||||
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||
hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "</ol>\n" : "</ul>\n"), 6);
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_listitem(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)
|
||||
{
|
||||
HOEDOWN_BUFPUTSL(ob, "<li>");
|
||||
if (content) {
|
||||
size_t size = content->size;
|
||||
while (size && content->data[size - 1] == '\n')
|
||||
size--;
|
||||
|
||||
hoedown_buffer_put(ob, content->data, size);
|
||||
}
|
||||
HOEDOWN_BUFPUTSL(ob, "</li>\n");
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_paragraph(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
size_t i = 0;
|
||||
|
||||
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||
|
||||
if (!content || !content->size)
|
||||
return;
|
||||
|
||||
while (i < content->size && isspace(content->data[i])) i++;
|
||||
|
||||
if (i == content->size)
|
||||
return;
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "<p>");
|
||||
if (state->flags & HOEDOWN_HTML_HARD_WRAP) {
|
||||
size_t org;
|
||||
while (i < content->size) {
|
||||
org = i;
|
||||
while (i < content->size && content->data[i] != '\n')
|
||||
i++;
|
||||
|
||||
if (i > org)
|
||||
hoedown_buffer_put(ob, content->data + org, i - org);
|
||||
|
||||
/*
|
||||
* do not insert a line break if this newline
|
||||
* is the last character on the paragraph
|
||||
*/
|
||||
if (i >= content->size - 1)
|
||||
break;
|
||||
|
||||
rndr_linebreak(ob, data);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
hoedown_buffer_put(ob, content->data + i, content->size - i);
|
||||
}
|
||||
HOEDOWN_BUFPUTSL(ob, "</p>\n");
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_raw_block(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)
|
||||
{
|
||||
size_t org, sz;
|
||||
|
||||
if (!text)
|
||||
return;
|
||||
|
||||
/* FIXME: Do we *really* need to trim the HTML? How does that make a difference? */
|
||||
sz = text->size;
|
||||
while (sz > 0 && text->data[sz - 1] == '\n')
|
||||
sz--;
|
||||
|
||||
org = 0;
|
||||
while (org < sz && text->data[org] == '\n')
|
||||
org++;
|
||||
|
||||
if (org >= sz)
|
||||
return;
|
||||
|
||||
if (ob->size)
|
||||
hoedown_buffer_putc(ob, '\n');
|
||||
|
||||
hoedown_buffer_put(ob, text->data + org, sz - org);
|
||||
hoedown_buffer_putc(ob, '\n');
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_triple_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (!content || !content->size) return 0;
|
||||
HOEDOWN_BUFPUTSL(ob, "<strong><em>");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</em></strong>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_hrule(hoedown_buffer *ob, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||
hoedown_buffer_puts(ob, USE_XHTML(state) ? "<hr/>\n" : "<hr>\n");
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_image(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
if (!link || !link->size) return 0;
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "<img src=\"");
|
||||
escape_href(ob, link->data, link->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "\" alt=\"");
|
||||
|
||||
if (alt && alt->size)
|
||||
escape_html(ob, alt->data, alt->size);
|
||||
|
||||
if (title && title->size) {
|
||||
HOEDOWN_BUFPUTSL(ob, "\" title=\"");
|
||||
escape_html(ob, title->data, title->size); }
|
||||
|
||||
hoedown_buffer_puts(ob, USE_XHTML(state) ? "\"/>" : "\">");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_raw_html(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
|
||||
/* ESCAPE overrides SKIP_HTML. It doesn't look to see if
|
||||
* there are any valid tags, just escapes all of them. */
|
||||
if((state->flags & HOEDOWN_HTML_ESCAPE) != 0) {
|
||||
escape_html(ob, text->data, text->size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((state->flags & HOEDOWN_HTML_SKIP_HTML) != 0)
|
||||
return 1;
|
||||
|
||||
hoedown_buffer_put(ob, text->data, text->size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_table(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||
HOEDOWN_BUFPUTSL(ob, "<table>\n");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</table>\n");
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_table_header(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||
HOEDOWN_BUFPUTSL(ob, "<thead>\n");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</thead>\n");
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_table_body(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||
HOEDOWN_BUFPUTSL(ob, "<tbody>\n");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</tbody>\n");
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_tablerow(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
HOEDOWN_BUFPUTSL(ob, "<tr>\n");
|
||||
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</tr>\n");
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_tablecell(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (flags & HOEDOWN_TABLE_HEADER) {
|
||||
HOEDOWN_BUFPUTSL(ob, "<th");
|
||||
} else {
|
||||
HOEDOWN_BUFPUTSL(ob, "<td");
|
||||
}
|
||||
|
||||
switch (flags & HOEDOWN_TABLE_ALIGNMASK) {
|
||||
case HOEDOWN_TABLE_ALIGN_CENTER:
|
||||
HOEDOWN_BUFPUTSL(ob, " style=\"text-align: center\">");
|
||||
break;
|
||||
|
||||
case HOEDOWN_TABLE_ALIGN_LEFT:
|
||||
HOEDOWN_BUFPUTSL(ob, " style=\"text-align: left\">");
|
||||
break;
|
||||
|
||||
case HOEDOWN_TABLE_ALIGN_RIGHT:
|
||||
HOEDOWN_BUFPUTSL(ob, " style=\"text-align: right\">");
|
||||
break;
|
||||
|
||||
default:
|
||||
HOEDOWN_BUFPUTSL(ob, ">");
|
||||
}
|
||||
|
||||
if (content)
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
|
||||
if (flags & HOEDOWN_TABLE_HEADER) {
|
||||
HOEDOWN_BUFPUTSL(ob, "</th>\n");
|
||||
} else {
|
||||
HOEDOWN_BUFPUTSL(ob, "</td>\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_superscript(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (!content || !content->size) return 0;
|
||||
HOEDOWN_BUFPUTSL(ob, "<sup>");
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</sup>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_normal_text(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (content)
|
||||
escape_html(ob, content->data, content->size);
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_footnotes(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
|
||||
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||
HOEDOWN_BUFPUTSL(ob, "<div class=\"footnotes\">\n");
|
||||
hoedown_buffer_puts(ob, USE_XHTML(state) ? "<hr/>\n" : "<hr>\n");
|
||||
HOEDOWN_BUFPUTSL(ob, "<ol>\n");
|
||||
|
||||
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||
|
||||
HOEDOWN_BUFPUTSL(ob, "\n</ol>\n</div>\n");
|
||||
}
|
||||
|
||||
static void
|
||||
rndr_footnote_def(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data)
|
||||
{
|
||||
size_t i = 0;
|
||||
int pfound = 0;
|
||||
|
||||
/* insert anchor at the end of first paragraph block */
|
||||
if (content) {
|
||||
while ((i+3) < content->size) {
|
||||
if (content->data[i++] != '<') continue;
|
||||
if (content->data[i++] != '/') continue;
|
||||
if (content->data[i++] != 'p' && content->data[i] != 'P') continue;
|
||||
if (content->data[i] != '>') continue;
|
||||
i -= 3;
|
||||
pfound = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hoedown_buffer_printf(ob, "\n<li id=\"fn%d\">\n", num);
|
||||
if (pfound) {
|
||||
hoedown_buffer_put(ob, content->data, i);
|
||||
hoedown_buffer_printf(ob, " <a href=\"#fnref%d\" rev=\"footnote\">↩</a>", num);
|
||||
hoedown_buffer_put(ob, content->data + i, content->size - i);
|
||||
} else if (content) {
|
||||
hoedown_buffer_put(ob, content->data, content->size);
|
||||
}
|
||||
HOEDOWN_BUFPUTSL(ob, "</li>\n");
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_footnote_ref(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_buffer_printf(ob, "<sup id=\"fnref%d\"><a href=\"#fn%d\" rel=\"footnote\">%d</a></sup>", num, num, num);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
rndr_math(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\[" : "\\("), 2);
|
||||
escape_html(ob, text->data, text->size);
|
||||
hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\]" : "\\)"), 2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
toc_header(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state = data->opaque;
|
||||
|
||||
if (level <= state->toc_data.nesting_level) {
|
||||
/* set the level offset if this is the first header
|
||||
* we're parsing for the document */
|
||||
if (state->toc_data.current_level == 0)
|
||||
state->toc_data.level_offset = level - 1;
|
||||
|
||||
level -= state->toc_data.level_offset;
|
||||
|
||||
if (level > state->toc_data.current_level) {
|
||||
while (level > state->toc_data.current_level) {
|
||||
HOEDOWN_BUFPUTSL(ob, "<ul>\n<li>\n");
|
||||
state->toc_data.current_level++;
|
||||
}
|
||||
} else if (level < state->toc_data.current_level) {
|
||||
HOEDOWN_BUFPUTSL(ob, "</li>\n");
|
||||
while (level < state->toc_data.current_level) {
|
||||
HOEDOWN_BUFPUTSL(ob, "</ul>\n</li>\n");
|
||||
state->toc_data.current_level--;
|
||||
}
|
||||
HOEDOWN_BUFPUTSL(ob,"<li>\n");
|
||||
} else {
|
||||
HOEDOWN_BUFPUTSL(ob,"</li>\n<li>\n");
|
||||
}
|
||||
|
||||
hoedown_buffer_printf(ob, "<a href=\"#toc_%d\">", state->toc_data.header_count++);
|
||||
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||
HOEDOWN_BUFPUTSL(ob, "</a>\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
toc_link(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data)
|
||||
{
|
||||
if (content && content->size) hoedown_buffer_put(ob, content->data, content->size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
toc_finalize(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data)
|
||||
{
|
||||
hoedown_html_renderer_state *state;
|
||||
|
||||
if (inline_render)
|
||||
return;
|
||||
|
||||
state = data->opaque;
|
||||
|
||||
while (state->toc_data.current_level > 0) {
|
||||
HOEDOWN_BUFPUTSL(ob, "</li>\n</ul>\n");
|
||||
state->toc_data.current_level--;
|
||||
}
|
||||
|
||||
state->toc_data.header_count = 0;
|
||||
}
|
||||
|
||||
hoedown_renderer *
|
||||
hoedown_html_toc_renderer_new(int nesting_level)
|
||||
{
|
||||
static const hoedown_renderer cb_default = {
|
||||
NULL,
|
||||
|
||||
NULL,
|
||||
NULL,
|
||||
toc_header,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
|
||||
NULL,
|
||||
rndr_codespan,
|
||||
rndr_double_emphasis,
|
||||
rndr_emphasis,
|
||||
rndr_underline,
|
||||
rndr_highlight,
|
||||
rndr_quote,
|
||||
NULL,
|
||||
NULL,
|
||||
toc_link,
|
||||
rndr_triple_emphasis,
|
||||
rndr_strikethrough,
|
||||
rndr_superscript,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
|
||||
NULL,
|
||||
rndr_normal_text,
|
||||
|
||||
NULL,
|
||||
toc_finalize
|
||||
};
|
||||
|
||||
hoedown_html_renderer_state *state;
|
||||
hoedown_renderer *renderer;
|
||||
|
||||
/* Prepare the state pointer */
|
||||
state = hoedown_malloc(sizeof(hoedown_html_renderer_state));
|
||||
memset(state, 0x0, sizeof(hoedown_html_renderer_state));
|
||||
|
||||
state->toc_data.nesting_level = nesting_level;
|
||||
|
||||
/* Prepare the renderer */
|
||||
renderer = hoedown_malloc(sizeof(hoedown_renderer));
|
||||
memcpy(renderer, &cb_default, sizeof(hoedown_renderer));
|
||||
|
||||
renderer->opaque = state;
|
||||
return renderer;
|
||||
}
|
||||
|
||||
hoedown_renderer *
|
||||
hoedown_html_renderer_new(hoedown_html_flags render_flags, int nesting_level)
|
||||
{
|
||||
static const hoedown_renderer cb_default = {
|
||||
NULL,
|
||||
|
||||
rndr_blockcode,
|
||||
rndr_blockquote,
|
||||
rndr_header,
|
||||
rndr_hrule,
|
||||
rndr_list,
|
||||
rndr_listitem,
|
||||
rndr_paragraph,
|
||||
rndr_table,
|
||||
rndr_table_header,
|
||||
rndr_table_body,
|
||||
rndr_tablerow,
|
||||
rndr_tablecell,
|
||||
rndr_footnotes,
|
||||
rndr_footnote_def,
|
||||
rndr_raw_block,
|
||||
|
||||
rndr_autolink,
|
||||
rndr_codespan,
|
||||
rndr_double_emphasis,
|
||||
rndr_emphasis,
|
||||
rndr_underline,
|
||||
rndr_highlight,
|
||||
rndr_quote,
|
||||
rndr_image,
|
||||
rndr_linebreak,
|
||||
rndr_link,
|
||||
rndr_triple_emphasis,
|
||||
rndr_strikethrough,
|
||||
rndr_superscript,
|
||||
rndr_footnote_ref,
|
||||
rndr_math,
|
||||
rndr_raw_html,
|
||||
|
||||
NULL,
|
||||
rndr_normal_text,
|
||||
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
hoedown_html_renderer_state *state;
|
||||
hoedown_renderer *renderer;
|
||||
|
||||
/* Prepare the state pointer */
|
||||
state = hoedown_malloc(sizeof(hoedown_html_renderer_state));
|
||||
memset(state, 0x0, sizeof(hoedown_html_renderer_state));
|
||||
|
||||
state->flags = render_flags;
|
||||
state->toc_data.nesting_level = nesting_level;
|
||||
|
||||
/* Prepare the renderer */
|
||||
renderer = hoedown_malloc(sizeof(hoedown_renderer));
|
||||
memcpy(renderer, &cb_default, sizeof(hoedown_renderer));
|
||||
|
||||
if (render_flags & HOEDOWN_HTML_SKIP_HTML || render_flags & HOEDOWN_HTML_ESCAPE)
|
||||
renderer->blockhtml = NULL;
|
||||
|
||||
renderer->opaque = state;
|
||||
return renderer;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_html_renderer_free(hoedown_renderer *renderer)
|
||||
{
|
||||
free(renderer->opaque);
|
||||
free(renderer);
|
||||
}
|
240
libraries/hoedown/src/html_blocks.c
Normal file
240
libraries/hoedown/src/html_blocks.c
Normal file
@ -0,0 +1,240 @@
|
||||
/* ANSI-C code produced by gperf version 3.0.3 */
|
||||
/* Command-line: gperf -L ANSI-C -N hoedown_find_block_tag -c -C -E -S 1 --ignore-case -m100 html_block_names.gperf */
|
||||
/* Computed positions: -k'1-2' */
|
||||
|
||||
#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
|
||||
&& ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
|
||||
&& (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
|
||||
&& ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
|
||||
&& ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
|
||||
&& ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
|
||||
&& ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
|
||||
&& ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
|
||||
&& ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
|
||||
&& ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
|
||||
&& ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
|
||||
&& ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
|
||||
&& ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
|
||||
&& ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
|
||||
&& ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
|
||||
&& ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
|
||||
&& ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
|
||||
&& ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
|
||||
&& ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
|
||||
&& ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
|
||||
&& ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
|
||||
&& ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
|
||||
&& ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
|
||||
/* The character set is not based on ISO-646. */
|
||||
#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
|
||||
#endif
|
||||
|
||||
/* maximum key range = 24, duplicates = 0 */
|
||||
|
||||
#ifndef GPERF_DOWNCASE
|
||||
#define GPERF_DOWNCASE 1
|
||||
static unsigned char gperf_downcase[256] =
|
||||
{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
|
||||
45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
|
||||
60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
|
||||
107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
|
||||
122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
|
||||
105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
|
||||
120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
|
||||
135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
|
||||
150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
|
||||
165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
|
||||
180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
|
||||
195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
|
||||
210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
|
||||
225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
|
||||
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
|
||||
255
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef GPERF_CASE_STRNCMP
|
||||
#define GPERF_CASE_STRNCMP 1
|
||||
static int
|
||||
gperf_case_strncmp (register const char *s1, register const char *s2, register unsigned int n)
|
||||
{
|
||||
for (; n > 0;)
|
||||
{
|
||||
unsigned char c1 = gperf_downcase[(unsigned char)*s1++];
|
||||
unsigned char c2 = gperf_downcase[(unsigned char)*s2++];
|
||||
if (c1 != 0 && c1 == c2)
|
||||
{
|
||||
n--;
|
||||
continue;
|
||||
}
|
||||
return (int)c1 - (int)c2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static unsigned int
|
||||
hash (register const char *str, register unsigned int len)
|
||||
{
|
||||
static const unsigned char asso_values[] =
|
||||
{
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
22, 21, 19, 18, 16, 0, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 1, 25, 0, 25,
|
||||
1, 0, 0, 13, 0, 25, 25, 11, 2, 1,
|
||||
0, 25, 25, 5, 0, 2, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 1, 25,
|
||||
0, 25, 1, 0, 0, 13, 0, 25, 25, 11,
|
||||
2, 1, 0, 25, 25, 5, 0, 2, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||
25, 25, 25, 25, 25, 25, 25
|
||||
};
|
||||
register int hval = (int)len;
|
||||
|
||||
switch (hval)
|
||||
{
|
||||
default:
|
||||
hval += asso_values[(unsigned char)str[1]+1];
|
||||
/*FALLTHROUGH*/
|
||||
case 1:
|
||||
hval += asso_values[(unsigned char)str[0]];
|
||||
break;
|
||||
}
|
||||
return hval;
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#ifdef __GNUC_STDC_INLINE__
|
||||
__attribute__ ((__gnu_inline__))
|
||||
#endif
|
||||
#endif
|
||||
const char *
|
||||
hoedown_find_block_tag (register const char *str, register unsigned int len)
|
||||
{
|
||||
enum
|
||||
{
|
||||
TOTAL_KEYWORDS = 24,
|
||||
MIN_WORD_LENGTH = 1,
|
||||
MAX_WORD_LENGTH = 10,
|
||||
MIN_HASH_VALUE = 1,
|
||||
MAX_HASH_VALUE = 24
|
||||
};
|
||||
|
||||
if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
|
||||
{
|
||||
register int key = hash (str, len);
|
||||
|
||||
if (key <= MAX_HASH_VALUE && key >= MIN_HASH_VALUE)
|
||||
{
|
||||
register const char *resword;
|
||||
|
||||
switch (key - 1)
|
||||
{
|
||||
case 0:
|
||||
resword = "p";
|
||||
goto compare;
|
||||
case 1:
|
||||
resword = "h6";
|
||||
goto compare;
|
||||
case 2:
|
||||
resword = "div";
|
||||
goto compare;
|
||||
case 3:
|
||||
resword = "del";
|
||||
goto compare;
|
||||
case 4:
|
||||
resword = "form";
|
||||
goto compare;
|
||||
case 5:
|
||||
resword = "table";
|
||||
goto compare;
|
||||
case 6:
|
||||
resword = "figure";
|
||||
goto compare;
|
||||
case 7:
|
||||
resword = "pre";
|
||||
goto compare;
|
||||
case 8:
|
||||
resword = "fieldset";
|
||||
goto compare;
|
||||
case 9:
|
||||
resword = "noscript";
|
||||
goto compare;
|
||||
case 10:
|
||||
resword = "script";
|
||||
goto compare;
|
||||
case 11:
|
||||
resword = "style";
|
||||
goto compare;
|
||||
case 12:
|
||||
resword = "dl";
|
||||
goto compare;
|
||||
case 13:
|
||||
resword = "ol";
|
||||
goto compare;
|
||||
case 14:
|
||||
resword = "ul";
|
||||
goto compare;
|
||||
case 15:
|
||||
resword = "math";
|
||||
goto compare;
|
||||
case 16:
|
||||
resword = "ins";
|
||||
goto compare;
|
||||
case 17:
|
||||
resword = "h5";
|
||||
goto compare;
|
||||
case 18:
|
||||
resword = "iframe";
|
||||
goto compare;
|
||||
case 19:
|
||||
resword = "h4";
|
||||
goto compare;
|
||||
case 20:
|
||||
resword = "h3";
|
||||
goto compare;
|
||||
case 21:
|
||||
resword = "blockquote";
|
||||
goto compare;
|
||||
case 22:
|
||||
resword = "h2";
|
||||
goto compare;
|
||||
case 23:
|
||||
resword = "h1";
|
||||
goto compare;
|
||||
}
|
||||
return 0;
|
||||
compare:
|
||||
if ((((unsigned char)*str ^ (unsigned char)*resword) & ~32) == 0 && !gperf_case_strncmp (str, resword, len) && resword[len] == '\0')
|
||||
return resword;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
435
libraries/hoedown/src/html_smartypants.c
Normal file
435
libraries/hoedown/src/html_smartypants.c
Normal file
@ -0,0 +1,435 @@
|
||||
#include "hoedown/html.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
struct smartypants_data {
|
||||
int in_squote;
|
||||
int in_dquote;
|
||||
};
|
||||
|
||||
static size_t smartypants_cb__ltag(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
static size_t smartypants_cb__dquote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
static size_t smartypants_cb__amp(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
static size_t smartypants_cb__period(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
static size_t smartypants_cb__number(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
static size_t smartypants_cb__dash(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
static size_t smartypants_cb__parens(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
static size_t smartypants_cb__squote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
static size_t smartypants_cb__backtick(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
static size_t smartypants_cb__escape(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||
|
||||
static size_t (*smartypants_cb_ptrs[])
|
||||
(hoedown_buffer *, struct smartypants_data *, uint8_t, const uint8_t *, size_t) =
|
||||
{
|
||||
NULL, /* 0 */
|
||||
smartypants_cb__dash, /* 1 */
|
||||
smartypants_cb__parens, /* 2 */
|
||||
smartypants_cb__squote, /* 3 */
|
||||
smartypants_cb__dquote, /* 4 */
|
||||
smartypants_cb__amp, /* 5 */
|
||||
smartypants_cb__period, /* 6 */
|
||||
smartypants_cb__number, /* 7 */
|
||||
smartypants_cb__ltag, /* 8 */
|
||||
smartypants_cb__backtick, /* 9 */
|
||||
smartypants_cb__escape, /* 10 */
|
||||
};
|
||||
|
||||
static const uint8_t smartypants_cb_chars[UINT8_MAX+1] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 4, 0, 0, 0, 5, 3, 2, 0, 0, 0, 0, 1, 6, 0,
|
||||
0, 7, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0,
|
||||
9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
static int
|
||||
word_boundary(uint8_t c)
|
||||
{
|
||||
return c == 0 || isspace(c) || ispunct(c);
|
||||
}
|
||||
|
||||
/*
|
||||
If 'text' begins with any kind of single quote (e.g. "'" or "'" etc.),
|
||||
returns the length of the sequence of characters that makes up the single-
|
||||
quote. Otherwise, returns zero.
|
||||
*/
|
||||
static size_t
|
||||
squote_len(const uint8_t *text, size_t size)
|
||||
{
|
||||
static char* single_quote_list[] = { "'", "'", "'", "'", NULL };
|
||||
char** p;
|
||||
|
||||
for (p = single_quote_list; *p; ++p) {
|
||||
size_t len = strlen(*p);
|
||||
if (size >= len && memcmp(text, *p, len) == 0) {
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Converts " or ' at very beginning or end of a word to left or right quote */
|
||||
static int
|
||||
smartypants_quotes(hoedown_buffer *ob, uint8_t previous_char, uint8_t next_char, uint8_t quote, int *is_open)
|
||||
{
|
||||
char ent[8];
|
||||
|
||||
if (*is_open && !word_boundary(next_char))
|
||||
return 0;
|
||||
|
||||
if (!(*is_open) && !word_boundary(previous_char))
|
||||
return 0;
|
||||
|
||||
snprintf(ent, sizeof(ent), "&%c%cquo;", (*is_open) ? 'r' : 'l', quote);
|
||||
*is_open = !(*is_open);
|
||||
hoedown_buffer_puts(ob, ent);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Converts ' to left or right single quote; but the initial ' might be in
|
||||
different forms, e.g. ' or ' or '.
|
||||
'squote_text' points to the original single quote, and 'squote_size' is its length.
|
||||
'text' points at the last character of the single-quote, e.g. ' or ;
|
||||
*/
|
||||
static size_t
|
||||
smartypants_squote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size,
|
||||
const uint8_t *squote_text, size_t squote_size)
|
||||
{
|
||||
if (size >= 2) {
|
||||
uint8_t t1 = tolower(text[1]);
|
||||
size_t next_squote_len = squote_len(text+1, size-1);
|
||||
|
||||
/* convert '' to “ or ” */
|
||||
if (next_squote_len > 0) {
|
||||
uint8_t next_char = (size > 1+next_squote_len) ? text[1+next_squote_len] : 0;
|
||||
if (smartypants_quotes(ob, previous_char, next_char, 'd', &smrt->in_dquote))
|
||||
return next_squote_len;
|
||||
}
|
||||
|
||||
/* Tom's, isn't, I'm, I'd */
|
||||
if ((t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') &&
|
||||
(size == 3 || word_boundary(text[2]))) {
|
||||
HOEDOWN_BUFPUTSL(ob, "’");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* you're, you'll, you've */
|
||||
if (size >= 3) {
|
||||
uint8_t t2 = tolower(text[2]);
|
||||
|
||||
if (((t1 == 'r' && t2 == 'e') ||
|
||||
(t1 == 'l' && t2 == 'l') ||
|
||||
(t1 == 'v' && t2 == 'e')) &&
|
||||
(size == 4 || word_boundary(text[3]))) {
|
||||
HOEDOWN_BUFPUTSL(ob, "’");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 's', &smrt->in_squote))
|
||||
return 0;
|
||||
|
||||
hoedown_buffer_put(ob, squote_text, squote_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Converts ' to left or right single quote. */
|
||||
static size_t
|
||||
smartypants_cb__squote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
return smartypants_squote(ob, smrt, previous_char, text, size, text, 1);
|
||||
}
|
||||
|
||||
/* Converts (c), (r), (tm) */
|
||||
static size_t
|
||||
smartypants_cb__parens(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
if (size >= 3) {
|
||||
uint8_t t1 = tolower(text[1]);
|
||||
uint8_t t2 = tolower(text[2]);
|
||||
|
||||
if (t1 == 'c' && t2 == ')') {
|
||||
HOEDOWN_BUFPUTSL(ob, "©");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (t1 == 'r' && t2 == ')') {
|
||||
HOEDOWN_BUFPUTSL(ob, "®");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (size >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')') {
|
||||
HOEDOWN_BUFPUTSL(ob, "™");
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
hoedown_buffer_putc(ob, text[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Converts "--" to em-dash, etc. */
|
||||
static size_t
|
||||
smartypants_cb__dash(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
if (size >= 3 && text[1] == '-' && text[2] == '-') {
|
||||
HOEDOWN_BUFPUTSL(ob, "—");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (size >= 2 && text[1] == '-') {
|
||||
HOEDOWN_BUFPUTSL(ob, "–");
|
||||
return 1;
|
||||
}
|
||||
|
||||
hoedown_buffer_putc(ob, text[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Converts " etc. */
|
||||
static size_t
|
||||
smartypants_cb__amp(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
size_t len;
|
||||
if (size >= 6 && memcmp(text, """, 6) == 0) {
|
||||
if (smartypants_quotes(ob, previous_char, size >= 7 ? text[6] : 0, 'd', &smrt->in_dquote))
|
||||
return 5;
|
||||
}
|
||||
|
||||
len = squote_len(text, size);
|
||||
if (len > 0) {
|
||||
return (len-1) + smartypants_squote(ob, smrt, previous_char, text+(len-1), size-(len-1), text, len);
|
||||
}
|
||||
|
||||
if (size >= 4 && memcmp(text, "�", 4) == 0)
|
||||
return 3;
|
||||
|
||||
hoedown_buffer_putc(ob, '&');
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Converts "..." to ellipsis */
|
||||
static size_t
|
||||
smartypants_cb__period(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
if (size >= 3 && text[1] == '.' && text[2] == '.') {
|
||||
HOEDOWN_BUFPUTSL(ob, "…");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (size >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.') {
|
||||
HOEDOWN_BUFPUTSL(ob, "…");
|
||||
return 4;
|
||||
}
|
||||
|
||||
hoedown_buffer_putc(ob, text[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Converts `` to opening double quote */
|
||||
static size_t
|
||||
smartypants_cb__backtick(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
if (size >= 2 && text[1] == '`') {
|
||||
if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote))
|
||||
return 1;
|
||||
}
|
||||
|
||||
hoedown_buffer_putc(ob, text[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Converts 1/2, 1/4, 3/4 */
|
||||
static size_t
|
||||
smartypants_cb__number(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
if (word_boundary(previous_char) && size >= 3) {
|
||||
if (text[0] == '1' && text[1] == '/' && text[2] == '2') {
|
||||
if (size == 3 || word_boundary(text[3])) {
|
||||
HOEDOWN_BUFPUTSL(ob, "½");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (text[0] == '1' && text[1] == '/' && text[2] == '4') {
|
||||
if (size == 3 || word_boundary(text[3]) ||
|
||||
(size >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h')) {
|
||||
HOEDOWN_BUFPUTSL(ob, "¼");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (text[0] == '3' && text[1] == '/' && text[2] == '4') {
|
||||
if (size == 3 || word_boundary(text[3]) ||
|
||||
(size >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's')) {
|
||||
HOEDOWN_BUFPUTSL(ob, "¾");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hoedown_buffer_putc(ob, text[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Converts " to left or right double quote */
|
||||
static size_t
|
||||
smartypants_cb__dquote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
if (!smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 'd', &smrt->in_dquote))
|
||||
HOEDOWN_BUFPUTSL(ob, """);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
smartypants_cb__ltag(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
static const char *skip_tags[] = {
|
||||
"pre", "code", "var", "samp", "kbd", "math", "script", "style"
|
||||
};
|
||||
static const size_t skip_tags_count = 8;
|
||||
|
||||
size_t tag, i = 0;
|
||||
|
||||
/* This is a comment. Copy everything verbatim until --> or EOF is seen. */
|
||||
if (i + 4 < size && memcmp(text, "<!--", 4) == 0) {
|
||||
i += 4;
|
||||
while (i + 3 < size && memcmp(text + i, "-->", 3) != 0)
|
||||
i++;
|
||||
i += 3;
|
||||
hoedown_buffer_put(ob, text, i + 1);
|
||||
return i;
|
||||
}
|
||||
|
||||
while (i < size && text[i] != '>')
|
||||
i++;
|
||||
|
||||
for (tag = 0; tag < skip_tags_count; ++tag) {
|
||||
if (hoedown_html_is_tag(text, size, skip_tags[tag]) == HOEDOWN_HTML_TAG_OPEN)
|
||||
break;
|
||||
}
|
||||
|
||||
if (tag < skip_tags_count) {
|
||||
for (;;) {
|
||||
while (i < size && text[i] != '<')
|
||||
i++;
|
||||
|
||||
if (i == size)
|
||||
break;
|
||||
|
||||
if (hoedown_html_is_tag(text + i, size - i, skip_tags[tag]) == HOEDOWN_HTML_TAG_CLOSE)
|
||||
break;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
while (i < size && text[i] != '>')
|
||||
i++;
|
||||
}
|
||||
|
||||
hoedown_buffer_put(ob, text, i + 1);
|
||||
return i;
|
||||
}
|
||||
|
||||
static size_t
|
||||
smartypants_cb__escape(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||
{
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
||||
switch (text[1]) {
|
||||
case '\\':
|
||||
case '"':
|
||||
case '\'':
|
||||
case '.':
|
||||
case '-':
|
||||
case '`':
|
||||
hoedown_buffer_putc(ob, text[1]);
|
||||
return 1;
|
||||
|
||||
default:
|
||||
hoedown_buffer_putc(ob, '\\');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
static struct {
|
||||
uint8_t c0;
|
||||
const uint8_t *pattern;
|
||||
const uint8_t *entity;
|
||||
int skip;
|
||||
} smartypants_subs[] = {
|
||||
{ '\'', "'s>", "’", 0 },
|
||||
{ '\'', "'t>", "’", 0 },
|
||||
{ '\'', "'re>", "’", 0 },
|
||||
{ '\'', "'ll>", "’", 0 },
|
||||
{ '\'', "'ve>", "’", 0 },
|
||||
{ '\'', "'m>", "’", 0 },
|
||||
{ '\'', "'d>", "’", 0 },
|
||||
{ '-', "--", "—", 1 },
|
||||
{ '-', "<->", "–", 0 },
|
||||
{ '.', "...", "…", 2 },
|
||||
{ '.', ". . .", "…", 4 },
|
||||
{ '(', "(c)", "©", 2 },
|
||||
{ '(', "(r)", "®", 2 },
|
||||
{ '(', "(tm)", "™", 3 },
|
||||
{ '3', "<3/4>", "¾", 2 },
|
||||
{ '3', "<3/4ths>", "¾", 2 },
|
||||
{ '1', "<1/2>", "½", 2 },
|
||||
{ '1', "<1/4>", "¼", 2 },
|
||||
{ '1', "<1/4th>", "¼", 2 },
|
||||
{ '&', "�", 0, 3 },
|
||||
};
|
||||
#endif
|
||||
|
||||
void
|
||||
hoedown_html_smartypants(hoedown_buffer *ob, const uint8_t *text, size_t size)
|
||||
{
|
||||
size_t i;
|
||||
struct smartypants_data smrt = {0, 0};
|
||||
|
||||
if (!text)
|
||||
return;
|
||||
|
||||
hoedown_buffer_grow(ob, size);
|
||||
|
||||
for (i = 0; i < size; ++i) {
|
||||
size_t org;
|
||||
uint8_t action = 0;
|
||||
|
||||
org = i;
|
||||
while (i < size && (action = smartypants_cb_chars[text[i]]) == 0)
|
||||
i++;
|
||||
|
||||
if (i > org)
|
||||
hoedown_buffer_put(ob, text + org, i - org);
|
||||
|
||||
if (i < size) {
|
||||
i += smartypants_cb_ptrs[(int)action]
|
||||
(ob, &smrt, i ? text[i - 1] : 0, text + i, size - i);
|
||||
}
|
||||
}
|
||||
}
|
79
libraries/hoedown/src/stack.c
Normal file
79
libraries/hoedown/src/stack.c
Normal file
@ -0,0 +1,79 @@
|
||||
#include "hoedown/stack.h"
|
||||
|
||||
#include "hoedown/buffer.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
void
|
||||
hoedown_stack_init(hoedown_stack *st, size_t initial_size)
|
||||
{
|
||||
assert(st);
|
||||
|
||||
st->item = NULL;
|
||||
st->size = st->asize = 0;
|
||||
|
||||
if (!initial_size)
|
||||
initial_size = 8;
|
||||
|
||||
hoedown_stack_grow(st, initial_size);
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_stack_uninit(hoedown_stack *st)
|
||||
{
|
||||
assert(st);
|
||||
|
||||
free(st->item);
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_stack_grow(hoedown_stack *st, size_t neosz)
|
||||
{
|
||||
assert(st);
|
||||
|
||||
if (st->asize >= neosz)
|
||||
return;
|
||||
|
||||
st->item = hoedown_realloc(st->item, neosz * sizeof(void *));
|
||||
memset(st->item + st->asize, 0x0, (neosz - st->asize) * sizeof(void *));
|
||||
|
||||
st->asize = neosz;
|
||||
|
||||
if (st->size > neosz)
|
||||
st->size = neosz;
|
||||
}
|
||||
|
||||
void
|
||||
hoedown_stack_push(hoedown_stack *st, void *item)
|
||||
{
|
||||
assert(st);
|
||||
|
||||
if (st->size >= st->asize)
|
||||
hoedown_stack_grow(st, st->size * 2);
|
||||
|
||||
st->item[st->size++] = item;
|
||||
}
|
||||
|
||||
void *
|
||||
hoedown_stack_pop(hoedown_stack *st)
|
||||
{
|
||||
assert(st);
|
||||
|
||||
if (!st->size)
|
||||
return NULL;
|
||||
|
||||
return st->item[--st->size];
|
||||
}
|
||||
|
||||
void *
|
||||
hoedown_stack_top(const hoedown_stack *st)
|
||||
{
|
||||
assert(st);
|
||||
|
||||
if (!st->size)
|
||||
return NULL;
|
||||
|
||||
return st->item[st->size - 1];
|
||||
}
|
9
libraries/hoedown/src/version.c
Normal file
9
libraries/hoedown/src/version.c
Normal file
@ -0,0 +1,9 @@
|
||||
#include "hoedown/version.h"
|
||||
|
||||
void
|
||||
hoedown_version(int *major, int *minor, int *revision)
|
||||
{
|
||||
*major = HOEDOWN_VERSION_MAJOR;
|
||||
*minor = HOEDOWN_VERSION_MINOR;
|
||||
*revision = HOEDOWN_VERSION_REVISION;
|
||||
}
|
18
libraries/iconfix/CMakeLists.txt
Normal file
18
libraries/iconfix/CMakeLists.txt
Normal file
@ -0,0 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project(iconfix)
|
||||
|
||||
find_package(Qt5Core REQUIRED QUIET)
|
||||
find_package(Qt5Widgets REQUIRED QUIET)
|
||||
|
||||
set(ICONFIX_SOURCES
|
||||
xdgicon.h
|
||||
xdgicon.cpp
|
||||
internal/qhexstring_p.h
|
||||
internal/qiconloader.cpp
|
||||
internal/qiconloader_p.h
|
||||
)
|
||||
|
||||
add_library(iconfix STATIC ${ICONFIX_SOURCES})
|
||||
target_include_directories(iconfix PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
qt5_use_modules(iconfix Core Widgets)
|
100
libraries/iconfix/internal/qhexstring_p.h
Normal file
100
libraries/iconfix/internal/qhexstring_p.h
Normal file
@ -0,0 +1,100 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtGui module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QtCore/qpoint.h>
|
||||
#include <QtCore/qstring.h>
|
||||
#include <QtGui/qpolygon.h>
|
||||
#include <QtCore/qstringbuilder.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
// internal helper. Converts an integer value to an unique string token
|
||||
template <typename T> struct HexString
|
||||
{
|
||||
inline HexString(const T t) : val(t)
|
||||
{
|
||||
}
|
||||
|
||||
inline void write(QChar *&dest) const
|
||||
{
|
||||
const ushort hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
const char *c = reinterpret_cast<const char *>(&val);
|
||||
for (uint i = 0; i < sizeof(T); ++i)
|
||||
{
|
||||
*dest++ = hexChars[*c & 0xf];
|
||||
*dest++ = hexChars[(*c & 0xf0) >> 4];
|
||||
++c;
|
||||
}
|
||||
}
|
||||
const T val;
|
||||
};
|
||||
|
||||
// specialization to enable fast concatenating of our string tokens to a string
|
||||
template <typename T> struct QConcatenable<HexString<T>>
|
||||
{
|
||||
typedef HexString<T> type;
|
||||
enum
|
||||
{
|
||||
ExactSize = true
|
||||
};
|
||||
static int size(const HexString<T> &)
|
||||
{
|
||||
return sizeof(T) * 2;
|
||||
}
|
||||
static inline void appendTo(const HexString<T> &str, QChar *&out)
|
||||
{
|
||||
str.write(out);
|
||||
}
|
||||
typedef QString ConvertTo;
|
||||
};
|
688
libraries/iconfix/internal/qiconloader.cpp
Normal file
688
libraries/iconfix/internal/qiconloader.cpp
Normal file
@ -0,0 +1,688 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtGui module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
#include "qiconloader_p.h"
|
||||
|
||||
#include <QtGui/QIconEnginePlugin>
|
||||
#include <QtGui/QPixmapCache>
|
||||
#include <QtGui/QIconEngine>
|
||||
#include <QtGui/QPalette>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QApplication>
|
||||
#include <QLatin1Literal>
|
||||
|
||||
#include "qhexstring_p.h"
|
||||
|
||||
namespace QtXdg
|
||||
{
|
||||
|
||||
Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance)
|
||||
|
||||
/* Theme to use in last resort, if the theme does not have the icon, neither the parents */
|
||||
|
||||
static QString fallbackTheme()
|
||||
{
|
||||
return QString("hicolor");
|
||||
}
|
||||
|
||||
QIconLoader::QIconLoader() : m_themeKey(1), m_supportsSvg(false), m_initialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
// We lazily initialize the loader to make static icons
|
||||
// work. Though we do not officially support this.
|
||||
|
||||
static inline QString systemThemeName()
|
||||
{
|
||||
return QIcon::themeName();
|
||||
}
|
||||
|
||||
static inline QStringList systemIconSearchPaths()
|
||||
{
|
||||
auto paths = QIcon::themeSearchPaths();
|
||||
paths.push_front(":/icons");
|
||||
return paths;
|
||||
}
|
||||
|
||||
void QIconLoader::ensureInitialized()
|
||||
{
|
||||
if (!m_initialized)
|
||||
{
|
||||
m_initialized = true;
|
||||
|
||||
Q_ASSERT(qApp);
|
||||
|
||||
m_systemTheme = QIcon::themeName();
|
||||
|
||||
if (m_systemTheme.isEmpty())
|
||||
m_systemTheme = fallbackTheme();
|
||||
m_supportsSvg = true;
|
||||
}
|
||||
}
|
||||
|
||||
QIconLoader *QIconLoader::instance()
|
||||
{
|
||||
iconLoaderInstance()->ensureInitialized();
|
||||
return iconLoaderInstance();
|
||||
}
|
||||
|
||||
// Queries the system theme and invalidates existing
|
||||
// icons if the theme has changed.
|
||||
void QIconLoader::updateSystemTheme()
|
||||
{
|
||||
// Only change if this is not explicitly set by the user
|
||||
if (m_userTheme.isEmpty())
|
||||
{
|
||||
QString theme = systemThemeName();
|
||||
if (theme.isEmpty())
|
||||
theme = fallbackTheme();
|
||||
if (theme != m_systemTheme)
|
||||
{
|
||||
m_systemTheme = theme;
|
||||
invalidateKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QIconLoader::setThemeName(const QString &themeName)
|
||||
{
|
||||
m_userTheme = themeName;
|
||||
invalidateKey();
|
||||
}
|
||||
|
||||
void QIconLoader::setThemeSearchPath(const QStringList &searchPaths)
|
||||
{
|
||||
m_iconDirs = searchPaths;
|
||||
themeList.clear();
|
||||
invalidateKey();
|
||||
}
|
||||
|
||||
QStringList QIconLoader::themeSearchPaths() const
|
||||
{
|
||||
if (m_iconDirs.isEmpty())
|
||||
{
|
||||
m_iconDirs = systemIconSearchPaths();
|
||||
}
|
||||
return m_iconDirs;
|
||||
}
|
||||
|
||||
QIconTheme::QIconTheme(const QString &themeName) : m_valid(false)
|
||||
{
|
||||
QFile themeIndex;
|
||||
|
||||
QStringList iconDirs = systemIconSearchPaths();
|
||||
for (int i = 0; i < iconDirs.size(); ++i)
|
||||
{
|
||||
QDir iconDir(iconDirs[i]);
|
||||
QString themeDir = iconDir.path() + QLatin1Char('/') + themeName;
|
||||
themeIndex.setFileName(themeDir + QLatin1String("/index.theme"));
|
||||
if (themeIndex.exists())
|
||||
{
|
||||
m_contentDir = themeDir;
|
||||
m_valid = true;
|
||||
|
||||
foreach (QString path, iconDirs)
|
||||
{
|
||||
if (QFileInfo(path).isDir())
|
||||
m_contentDirs.append(path + QLatin1Char('/') + themeName);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if there is no index file, abscond.
|
||||
if (!themeIndex.exists())
|
||||
return;
|
||||
|
||||
// otherwise continue reading index file
|
||||
const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat);
|
||||
QStringListIterator keyIterator(indexReader.allKeys());
|
||||
while (keyIterator.hasNext())
|
||||
{
|
||||
const QString key = keyIterator.next();
|
||||
if (!key.endsWith(QLatin1String("/Size")))
|
||||
continue;
|
||||
|
||||
// Note the QSettings ini-format does not accept
|
||||
// slashes in key names, hence we have to cheat
|
||||
int size = indexReader.value(key).toInt();
|
||||
if (!size)
|
||||
continue;
|
||||
|
||||
QString directoryKey = key.left(key.size() - 5);
|
||||
QIconDirInfo dirInfo(directoryKey);
|
||||
dirInfo.size = size;
|
||||
QString type =
|
||||
indexReader.value(directoryKey + QLatin1String("/Type")).toString();
|
||||
|
||||
if (type == QLatin1String("Fixed"))
|
||||
dirInfo.type = QIconDirInfo::Fixed;
|
||||
else if (type == QLatin1String("Scalable"))
|
||||
dirInfo.type = QIconDirInfo::Scalable;
|
||||
else
|
||||
dirInfo.type = QIconDirInfo::Threshold;
|
||||
|
||||
dirInfo.threshold =
|
||||
indexReader.value(directoryKey + QLatin1String("/Threshold"), 2)
|
||||
.toInt();
|
||||
|
||||
dirInfo.minSize =
|
||||
indexReader.value(directoryKey + QLatin1String("/MinSize"), size)
|
||||
.toInt();
|
||||
|
||||
dirInfo.maxSize =
|
||||
indexReader.value(directoryKey + QLatin1String("/MaxSize"), size)
|
||||
.toInt();
|
||||
m_keyList.append(dirInfo);
|
||||
}
|
||||
|
||||
// Parent themes provide fallbacks for missing icons
|
||||
m_parents = indexReader.value(QLatin1String("Icon Theme/Inherits")).toStringList();
|
||||
m_parents.removeAll(QString());
|
||||
|
||||
// Ensure a default platform fallback for all themes
|
||||
if (m_parents.isEmpty())
|
||||
{
|
||||
const QString fallback = fallbackTheme();
|
||||
if (!fallback.isEmpty())
|
||||
m_parents.append(fallback);
|
||||
}
|
||||
|
||||
// Ensure that all themes fall back to hicolor
|
||||
if (!m_parents.contains(QLatin1String("hicolor")))
|
||||
m_parents.append(QLatin1String("hicolor"));
|
||||
}
|
||||
|
||||
QThemeIconEntries QIconLoader::findIconHelper(const QString &themeName, const QString &iconName,
|
||||
QStringList &visited) const
|
||||
{
|
||||
QThemeIconEntries entries;
|
||||
Q_ASSERT(!themeName.isEmpty());
|
||||
|
||||
QPixmap pixmap;
|
||||
|
||||
// Used to protect against potential recursions
|
||||
visited << themeName;
|
||||
|
||||
QIconTheme theme = themeList.value(themeName);
|
||||
if (!theme.isValid())
|
||||
{
|
||||
theme = QIconTheme(themeName);
|
||||
if (!theme.isValid())
|
||||
theme = QIconTheme(fallbackTheme());
|
||||
|
||||
themeList.insert(themeName, theme);
|
||||
}
|
||||
|
||||
QStringList contentDirs = theme.contentDirs();
|
||||
const QVector<QIconDirInfo> subDirs = theme.keyList();
|
||||
|
||||
const QString svgext(QLatin1String(".svg"));
|
||||
const QString pngext(QLatin1String(".png"));
|
||||
const QString xpmext(QLatin1String(".xpm"));
|
||||
|
||||
// Add all relevant files
|
||||
for (int i = 0; i < subDirs.size(); ++i)
|
||||
{
|
||||
const QIconDirInfo &dirInfo = subDirs.at(i);
|
||||
QString subdir = dirInfo.path;
|
||||
|
||||
foreach (QString contentDir, contentDirs)
|
||||
{
|
||||
QDir currentDir(contentDir + '/' + subdir);
|
||||
|
||||
if (currentDir.exists(iconName + pngext))
|
||||
{
|
||||
PixmapEntry *iconEntry = new PixmapEntry;
|
||||
iconEntry->dir = dirInfo;
|
||||
iconEntry->filename = currentDir.filePath(iconName + pngext);
|
||||
// Notice we ensure that pixmap entries always come before
|
||||
// scalable to preserve search order afterwards
|
||||
entries.prepend(iconEntry);
|
||||
}
|
||||
else if (m_supportsSvg && currentDir.exists(iconName + svgext))
|
||||
{
|
||||
ScalableEntry *iconEntry = new ScalableEntry;
|
||||
iconEntry->dir = dirInfo;
|
||||
iconEntry->filename = currentDir.filePath(iconName + svgext);
|
||||
entries.append(iconEntry);
|
||||
break;
|
||||
}
|
||||
else if (currentDir.exists(iconName + xpmext))
|
||||
{
|
||||
PixmapEntry *iconEntry = new PixmapEntry;
|
||||
iconEntry->dir = dirInfo;
|
||||
iconEntry->filename = currentDir.filePath(iconName + xpmext);
|
||||
// Notice we ensure that pixmap entries always come before
|
||||
// scalable to preserve search order afterwards
|
||||
entries.append(iconEntry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.isEmpty())
|
||||
{
|
||||
const QStringList parents = theme.parents();
|
||||
// Search recursively through inherited themes
|
||||
for (int i = 0; i < parents.size(); ++i)
|
||||
{
|
||||
|
||||
const QString parentTheme = parents.at(i).trimmed();
|
||||
|
||||
if (!visited.contains(parentTheme)) // guard against recursion
|
||||
entries = findIconHelper(parentTheme, iconName, visited);
|
||||
|
||||
if (!entries.isEmpty()) // success
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
Author: Kaitlin Rupert <kaitlin.rupert@intel.com>
|
||||
Date: Aug 12, 2010
|
||||
Description: Make it so that the QIcon loader honors /usr/share/pixmaps
|
||||
directory. This is a valid directory per the Freedesktop.org
|
||||
icon theme specification.
|
||||
Bug: https://bugreports.qt.nokia.com/browse/QTBUG-12874
|
||||
*********************************************************************/
|
||||
#ifdef Q_OS_LINUX
|
||||
/* Freedesktop standard says to look in /usr/share/pixmaps last */
|
||||
if (entries.isEmpty())
|
||||
{
|
||||
const QString pixmaps(QLatin1String("/usr/share/pixmaps"));
|
||||
|
||||
QDir currentDir(pixmaps);
|
||||
QIconDirInfo dirInfo(pixmaps);
|
||||
if (currentDir.exists(iconName + pngext))
|
||||
{
|
||||
PixmapEntry *iconEntry = new PixmapEntry;
|
||||
iconEntry->dir = dirInfo;
|
||||
iconEntry->filename = currentDir.filePath(iconName + pngext);
|
||||
// Notice we ensure that pixmap entries always come before
|
||||
// scalable to preserve search order afterwards
|
||||
entries.prepend(iconEntry);
|
||||
}
|
||||
else if (m_supportsSvg && currentDir.exists(iconName + svgext))
|
||||
{
|
||||
ScalableEntry *iconEntry = new ScalableEntry;
|
||||
iconEntry->dir = dirInfo;
|
||||
iconEntry->filename = currentDir.filePath(iconName + svgext);
|
||||
entries.append(iconEntry);
|
||||
}
|
||||
else if (currentDir.exists(iconName + xpmext))
|
||||
{
|
||||
PixmapEntry *iconEntry = new PixmapEntry;
|
||||
iconEntry->dir = dirInfo;
|
||||
iconEntry->filename = currentDir.filePath(iconName + xpmext);
|
||||
// Notice we ensure that pixmap entries always come before
|
||||
// scalable to preserve search order afterwards
|
||||
entries.append(iconEntry);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (entries.isEmpty())
|
||||
{
|
||||
// Search for unthemed icons in main dir of search paths
|
||||
QStringList themeSearchPaths = QIcon::themeSearchPaths();
|
||||
foreach (QString contentDir, themeSearchPaths)
|
||||
{
|
||||
QDir currentDir(contentDir);
|
||||
|
||||
if (currentDir.exists(iconName + pngext))
|
||||
{
|
||||
PixmapEntry *iconEntry = new PixmapEntry;
|
||||
iconEntry->filename = currentDir.filePath(iconName + pngext);
|
||||
// Notice we ensure that pixmap entries always come before
|
||||
// scalable to preserve search order afterwards
|
||||
entries.prepend(iconEntry);
|
||||
}
|
||||
else if (m_supportsSvg && currentDir.exists(iconName + svgext))
|
||||
{
|
||||
ScalableEntry *iconEntry = new ScalableEntry;
|
||||
iconEntry->filename = currentDir.filePath(iconName + svgext);
|
||||
entries.append(iconEntry);
|
||||
break;
|
||||
}
|
||||
else if (currentDir.exists(iconName + xpmext))
|
||||
{
|
||||
PixmapEntry *iconEntry = new PixmapEntry;
|
||||
iconEntry->filename = currentDir.filePath(iconName + xpmext);
|
||||
// Notice we ensure that pixmap entries always come before
|
||||
// scalable to preserve search order afterwards
|
||||
entries.append(iconEntry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
QThemeIconEntries QIconLoader::loadIcon(const QString &name) const
|
||||
{
|
||||
if (!themeName().isEmpty())
|
||||
{
|
||||
QStringList visited;
|
||||
return findIconHelper(themeName(), name, visited);
|
||||
}
|
||||
|
||||
return QThemeIconEntries();
|
||||
}
|
||||
|
||||
// -------- Icon Loader Engine -------- //
|
||||
|
||||
QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QString &iconName)
|
||||
: m_iconName(iconName), m_key(0)
|
||||
{
|
||||
}
|
||||
|
||||
QIconLoaderEngineFixed::~QIconLoaderEngineFixed()
|
||||
{
|
||||
qDeleteAll(m_entries);
|
||||
}
|
||||
|
||||
QIconLoaderEngineFixed::QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other)
|
||||
: QIconEngine(other), m_iconName(other.m_iconName), m_key(0)
|
||||
{
|
||||
}
|
||||
|
||||
QIconEngine *QIconLoaderEngineFixed::clone() const
|
||||
{
|
||||
return new QIconLoaderEngineFixed(*this);
|
||||
}
|
||||
|
||||
bool QIconLoaderEngineFixed::read(QDataStream &in)
|
||||
{
|
||||
in >> m_iconName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QIconLoaderEngineFixed::write(QDataStream &out) const
|
||||
{
|
||||
out << m_iconName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QIconLoaderEngineFixed::hasIcon() const
|
||||
{
|
||||
return !(m_entries.isEmpty());
|
||||
}
|
||||
|
||||
// Lazily load the icon
|
||||
void QIconLoaderEngineFixed::ensureLoaded()
|
||||
{
|
||||
if (!(QIconLoader::instance()->themeKey() == m_key))
|
||||
{
|
||||
|
||||
qDeleteAll(m_entries);
|
||||
|
||||
m_entries = QIconLoader::instance()->loadIcon(m_iconName);
|
||||
m_key = QIconLoader::instance()->themeKey();
|
||||
}
|
||||
}
|
||||
|
||||
void QIconLoaderEngineFixed::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode,
|
||||
QIcon::State state)
|
||||
{
|
||||
QSize pixmapSize = rect.size();
|
||||
#if defined(Q_WS_MAC)
|
||||
pixmapSize *= qt_mac_get_scalefactor();
|
||||
#endif
|
||||
painter->drawPixmap(rect, pixmap(pixmapSize, mode, state));
|
||||
}
|
||||
|
||||
/*
|
||||
* This algorithm is defined by the freedesktop spec:
|
||||
* http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
|
||||
*/
|
||||
static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize)
|
||||
{
|
||||
if (dir.type == QIconDirInfo::Fixed)
|
||||
{
|
||||
return dir.size == iconsize;
|
||||
}
|
||||
else if (dir.type == QIconDirInfo::Scalable)
|
||||
{
|
||||
return dir.size <= dir.maxSize && iconsize >= dir.minSize;
|
||||
}
|
||||
else if (dir.type == QIconDirInfo::Threshold)
|
||||
{
|
||||
return iconsize >= dir.size - dir.threshold && iconsize <= dir.size + dir.threshold;
|
||||
}
|
||||
|
||||
Q_ASSERT(1); // Not a valid value
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This algorithm is defined by the freedesktop spec:
|
||||
* http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
|
||||
*/
|
||||
static int directorySizeDistance(const QIconDirInfo &dir, int iconsize)
|
||||
{
|
||||
if (dir.type == QIconDirInfo::Fixed)
|
||||
{
|
||||
return qAbs(dir.size - iconsize);
|
||||
}
|
||||
else if (dir.type == QIconDirInfo::Scalable)
|
||||
{
|
||||
if (iconsize < dir.minSize)
|
||||
return dir.minSize - iconsize;
|
||||
else if (iconsize > dir.maxSize)
|
||||
return iconsize - dir.maxSize;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else if (dir.type == QIconDirInfo::Threshold)
|
||||
{
|
||||
if (iconsize < dir.size - dir.threshold)
|
||||
return dir.minSize - iconsize;
|
||||
else if (iconsize > dir.size + dir.threshold)
|
||||
return iconsize - dir.maxSize;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
Q_ASSERT(1); // Not a valid value
|
||||
return INT_MAX;
|
||||
}
|
||||
|
||||
QIconLoaderEngineEntry *QIconLoaderEngineFixed::entryForSize(const QSize &size)
|
||||
{
|
||||
int iconsize = qMin(size.width(), size.height());
|
||||
|
||||
// Note that m_entries are sorted so that png-files
|
||||
// come first
|
||||
|
||||
const int numEntries = m_entries.size();
|
||||
|
||||
// Search for exact matches first
|
||||
for (int i = 0; i < numEntries; ++i)
|
||||
{
|
||||
QIconLoaderEngineEntry *entry = m_entries.at(i);
|
||||
if (directoryMatchesSize(entry->dir, iconsize))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the minimum distance icon
|
||||
int minimalSize = INT_MAX;
|
||||
QIconLoaderEngineEntry *closestMatch = 0;
|
||||
for (int i = 0; i < numEntries; ++i)
|
||||
{
|
||||
QIconLoaderEngineEntry *entry = m_entries.at(i);
|
||||
int distance = directorySizeDistance(entry->dir, iconsize);
|
||||
if (distance < minimalSize)
|
||||
{
|
||||
minimalSize = distance;
|
||||
closestMatch = entry;
|
||||
}
|
||||
}
|
||||
return closestMatch;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the actual icon size. For scalable svg's this is equivalent
|
||||
* to the requested size. Otherwise the closest match is returned but
|
||||
* we can never return a bigger size than the requested size.
|
||||
*
|
||||
*/
|
||||
QSize QIconLoaderEngineFixed::actualSize(const QSize &size, QIcon::Mode mode,
|
||||
QIcon::State state)
|
||||
{
|
||||
ensureLoaded();
|
||||
|
||||
QIconLoaderEngineEntry *entry = entryForSize(size);
|
||||
if (entry)
|
||||
{
|
||||
const QIconDirInfo &dir = entry->dir;
|
||||
if (dir.type == QIconDirInfo::Scalable)
|
||||
return size;
|
||||
else
|
||||
{
|
||||
int result = qMin<int>(dir.size, qMin(size.width(), size.height()));
|
||||
return QSize(result, result);
|
||||
}
|
||||
}
|
||||
return QIconEngine::actualSize(size, mode, state);
|
||||
}
|
||||
|
||||
QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
Q_UNUSED(state);
|
||||
|
||||
// Ensure that basePixmap is lazily initialized before generating the
|
||||
// key, otherwise the cache key is not unique
|
||||
if (basePixmap.isNull())
|
||||
basePixmap.load(filename);
|
||||
|
||||
QSize actualSize = basePixmap.size();
|
||||
if (!actualSize.isNull() &&
|
||||
(actualSize.width() > size.width() || actualSize.height() > size.height()))
|
||||
actualSize.scale(size, Qt::KeepAspectRatio);
|
||||
|
||||
QString key = QLatin1String("$qt_theme_") % HexString<qint64>(basePixmap.cacheKey()) %
|
||||
HexString<int>(mode) %
|
||||
HexString<qint64>(QGuiApplication::palette().cacheKey()) %
|
||||
HexString<int>(actualSize.width()) % HexString<int>(actualSize.height());
|
||||
|
||||
QPixmap cachedPixmap;
|
||||
if (QPixmapCache::find(key, &cachedPixmap))
|
||||
{
|
||||
return cachedPixmap;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (basePixmap.size() != actualSize)
|
||||
{
|
||||
cachedPixmap = basePixmap.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedPixmap = basePixmap;
|
||||
}
|
||||
QPixmapCache::insert(key, cachedPixmap);
|
||||
}
|
||||
return cachedPixmap;
|
||||
}
|
||||
|
||||
QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
if (svgIcon.isNull())
|
||||
{
|
||||
svgIcon = QIcon(filename);
|
||||
}
|
||||
|
||||
// Simply reuse svg icon engine
|
||||
return svgIcon.pixmap(size, mode, state);
|
||||
}
|
||||
|
||||
QPixmap QIconLoaderEngineFixed::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
ensureLoaded();
|
||||
|
||||
QIconLoaderEngineEntry *entry = entryForSize(size);
|
||||
if (entry)
|
||||
{
|
||||
return entry->pixmap(size, mode, state);
|
||||
}
|
||||
|
||||
return QPixmap();
|
||||
}
|
||||
|
||||
QString QIconLoaderEngineFixed::key() const
|
||||
{
|
||||
return QLatin1String("QIconLoaderEngineFixed");
|
||||
}
|
||||
|
||||
void QIconLoaderEngineFixed::virtual_hook(int id, void *data)
|
||||
{
|
||||
ensureLoaded();
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case QIconEngine::AvailableSizesHook:
|
||||
{
|
||||
QIconEngine::AvailableSizesArgument &arg =
|
||||
*reinterpret_cast<QIconEngine::AvailableSizesArgument *>(data);
|
||||
const int N = m_entries.size();
|
||||
QList<QSize> sizes;
|
||||
sizes.reserve(N);
|
||||
|
||||
// Gets all sizes from the DirectoryInfo entries
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
int size = m_entries.at(i)->dir.size;
|
||||
sizes.append(QSize(size, size));
|
||||
}
|
||||
arg.sizes.swap(sizes); // commit
|
||||
}
|
||||
break;
|
||||
case QIconEngine::IconNameHook:
|
||||
{
|
||||
QString &name = *reinterpret_cast<QString *>(data);
|
||||
name = m_iconName;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
QIconEngine::virtual_hook(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
} // QtXdg
|
219
libraries/iconfix/internal/qiconloader_p.h
Normal file
219
libraries/iconfix/internal/qiconloader_p.h
Normal file
@ -0,0 +1,219 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtGui module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <QtGui/QIcon>
|
||||
#include <QtGui/QIconEngine>
|
||||
#include <QtGui/QPixmapCache>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QVector>
|
||||
#include <QtCore/QTypeInfo>
|
||||
|
||||
|
||||
namespace QtXdg
|
||||
{
|
||||
|
||||
class QIconLoader;
|
||||
|
||||
struct QIconDirInfo
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
Fixed,
|
||||
Scalable,
|
||||
Threshold
|
||||
};
|
||||
QIconDirInfo(const QString &_path = QString())
|
||||
: path(_path), size(0), maxSize(0), minSize(0), threshold(0), type(Threshold)
|
||||
{
|
||||
}
|
||||
QString path;
|
||||
short size;
|
||||
short maxSize;
|
||||
short minSize;
|
||||
short threshold;
|
||||
Type type : 4;
|
||||
};
|
||||
|
||||
class QIconLoaderEngineEntry
|
||||
{
|
||||
public:
|
||||
virtual ~QIconLoaderEngineEntry()
|
||||
{
|
||||
}
|
||||
virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) = 0;
|
||||
QString filename;
|
||||
QIconDirInfo dir;
|
||||
static int count;
|
||||
};
|
||||
|
||||
struct ScalableEntry : public QIconLoaderEngineEntry
|
||||
{
|
||||
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE;
|
||||
QIcon svgIcon;
|
||||
};
|
||||
|
||||
struct PixmapEntry : public QIconLoaderEngineEntry
|
||||
{
|
||||
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) Q_DECL_OVERRIDE;
|
||||
QPixmap basePixmap;
|
||||
};
|
||||
|
||||
typedef QList<QIconLoaderEngineEntry *> QThemeIconEntries;
|
||||
|
||||
// class QIconLoaderEngine : public QIconEngine
|
||||
class QIconLoaderEngineFixed : public QIconEngine
|
||||
{
|
||||
public:
|
||||
QIconLoaderEngineFixed(const QString &iconName = QString());
|
||||
~QIconLoaderEngineFixed();
|
||||
|
||||
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state);
|
||||
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state);
|
||||
QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state);
|
||||
QIconEngine *clone() const;
|
||||
bool read(QDataStream &in);
|
||||
bool write(QDataStream &out) const;
|
||||
|
||||
private:
|
||||
QString key() const;
|
||||
bool hasIcon() const;
|
||||
void ensureLoaded();
|
||||
void virtual_hook(int id, void *data);
|
||||
QIconLoaderEngineEntry *entryForSize(const QSize &size);
|
||||
QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other);
|
||||
QThemeIconEntries m_entries;
|
||||
QString m_iconName;
|
||||
uint m_key;
|
||||
|
||||
friend class QIconLoader;
|
||||
};
|
||||
|
||||
class QIconTheme
|
||||
{
|
||||
public:
|
||||
QIconTheme(const QString &name);
|
||||
QIconTheme() : m_valid(false)
|
||||
{
|
||||
}
|
||||
QStringList parents()
|
||||
{
|
||||
return m_parents;
|
||||
}
|
||||
QVector<QIconDirInfo> keyList()
|
||||
{
|
||||
return m_keyList;
|
||||
}
|
||||
QString contentDir()
|
||||
{
|
||||
return m_contentDir;
|
||||
}
|
||||
QStringList contentDirs()
|
||||
{
|
||||
return m_contentDirs;
|
||||
}
|
||||
bool isValid()
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_contentDir;
|
||||
QStringList m_contentDirs;
|
||||
QVector<QIconDirInfo> m_keyList;
|
||||
QStringList m_parents;
|
||||
bool m_valid;
|
||||
};
|
||||
|
||||
class QIconLoader
|
||||
{
|
||||
public:
|
||||
QIconLoader();
|
||||
QThemeIconEntries loadIcon(const QString &iconName) const;
|
||||
uint themeKey() const
|
||||
{
|
||||
return m_themeKey;
|
||||
}
|
||||
|
||||
QString themeName() const
|
||||
{
|
||||
return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme;
|
||||
}
|
||||
void setThemeName(const QString &themeName);
|
||||
QIconTheme theme()
|
||||
{
|
||||
return themeList.value(themeName());
|
||||
}
|
||||
void setThemeSearchPath(const QStringList &searchPaths);
|
||||
QStringList themeSearchPaths() const;
|
||||
QIconDirInfo dirInfo(int dirindex);
|
||||
static QIconLoader *instance();
|
||||
void updateSystemTheme();
|
||||
void invalidateKey()
|
||||
{
|
||||
m_themeKey++;
|
||||
}
|
||||
void ensureInitialized();
|
||||
|
||||
private:
|
||||
QThemeIconEntries findIconHelper(const QString &themeName, const QString &iconName,
|
||||
QStringList &visited) const;
|
||||
uint m_themeKey;
|
||||
bool m_supportsSvg;
|
||||
bool m_initialized;
|
||||
|
||||
mutable QString m_userTheme;
|
||||
mutable QString m_systemTheme;
|
||||
mutable QStringList m_iconDirs;
|
||||
mutable QHash<QString, QIconTheme> themeList;
|
||||
};
|
||||
|
||||
} // QtXdg
|
||||
|
||||
// Note: class template specialization of 'QTypeInfo' must occur at
|
||||
// global scope
|
||||
Q_DECLARE_TYPEINFO(QtXdg::QIconDirInfo, Q_MOVABLE_TYPE);
|
152
libraries/iconfix/xdgicon.cpp
Normal file
152
libraries/iconfix/xdgicon.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
/* BEGIN_COMMON_COPYRIGHT_HEADER
|
||||
* (c)LGPL2+
|
||||
*
|
||||
* Razor - a lightweight, Qt based, desktop toolset
|
||||
* http://razor-qt.org
|
||||
*
|
||||
* Copyright: 2010-2011 Razor team
|
||||
* Authors:
|
||||
* Alexander Sokoloff <sokoloff.a@gmail.com>
|
||||
*
|
||||
* This program or library 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.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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 library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA
|
||||
*
|
||||
* END_COMMON_COPYRIGHT_HEADER */
|
||||
|
||||
#include "xdgicon.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QStringList>
|
||||
#include <QFileInfo>
|
||||
#include <QCache>
|
||||
#include "internal/qiconloader_p.h"
|
||||
#include <QCoreApplication>
|
||||
|
||||
/************************************************
|
||||
|
||||
************************************************/
|
||||
static void qt_cleanup_icon_cache();
|
||||
typedef QCache<QString, QIcon> IconCache;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct QtIconCache : public IconCache
|
||||
{
|
||||
QtIconCache()
|
||||
{
|
||||
qAddPostRoutine(qt_cleanup_icon_cache);
|
||||
}
|
||||
};
|
||||
}
|
||||
Q_GLOBAL_STATIC(IconCache, qtIconCache);
|
||||
|
||||
static void qt_cleanup_icon_cache()
|
||||
{
|
||||
qtIconCache()->clear();
|
||||
}
|
||||
|
||||
/************************************************
|
||||
|
||||
************************************************/
|
||||
XdgIcon::XdgIcon()
|
||||
{
|
||||
}
|
||||
|
||||
/************************************************
|
||||
|
||||
************************************************/
|
||||
XdgIcon::~XdgIcon()
|
||||
{
|
||||
}
|
||||
|
||||
/************************************************
|
||||
Returns the name of the current icon theme.
|
||||
************************************************/
|
||||
QString XdgIcon::themeName()
|
||||
{
|
||||
return QIcon::themeName();
|
||||
}
|
||||
|
||||
/************************************************
|
||||
Sets the current icon theme to name.
|
||||
************************************************/
|
||||
void XdgIcon::setThemeName(const QString &themeName)
|
||||
{
|
||||
QIcon::setThemeName(themeName);
|
||||
QtXdg::QIconLoader::instance()->updateSystemTheme();
|
||||
}
|
||||
|
||||
/************************************************
|
||||
Returns the QIcon corresponding to name in the current icon theme. If no such icon
|
||||
is found in the current theme fallback is return instead.
|
||||
************************************************/
|
||||
QIcon XdgIcon::fromTheme(const QString &iconName, const QIcon &fallback)
|
||||
{
|
||||
if (iconName.isEmpty())
|
||||
return fallback;
|
||||
|
||||
bool isAbsolute = (iconName[0] == '/');
|
||||
|
||||
QString name = QFileInfo(iconName).fileName();
|
||||
if (name.endsWith(".png", Qt::CaseInsensitive) ||
|
||||
name.endsWith(".svg", Qt::CaseInsensitive) ||
|
||||
name.endsWith(".xpm", Qt::CaseInsensitive))
|
||||
{
|
||||
name.truncate(name.length() - 4);
|
||||
}
|
||||
|
||||
QIcon icon;
|
||||
|
||||
if (qtIconCache()->contains(name))
|
||||
{
|
||||
icon = *qtIconCache()->object(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
QIcon *cachedIcon;
|
||||
if (!isAbsolute)
|
||||
cachedIcon = new QIcon(new QtXdg::QIconLoaderEngineFixed(name));
|
||||
else
|
||||
cachedIcon = new QIcon(iconName);
|
||||
qtIconCache()->insert(name, cachedIcon);
|
||||
icon = *cachedIcon;
|
||||
}
|
||||
|
||||
// Note the qapp check is to allow lazy loading of static icons
|
||||
// Supporting fallbacks will not work for this case.
|
||||
if (qApp && !isAbsolute && icon.availableSizes().isEmpty())
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
/************************************************
|
||||
Returns the QIcon corresponding to names in the current icon theme. If no such icon
|
||||
is found in the current theme fallback is return instead.
|
||||
************************************************/
|
||||
QIcon XdgIcon::fromTheme(const QStringList &iconNames, const QIcon &fallback)
|
||||
{
|
||||
foreach (QString iconName, iconNames)
|
||||
{
|
||||
QIcon icon = fromTheme(iconName);
|
||||
if (!icon.isNull())
|
||||
return icon;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
46
libraries/iconfix/xdgicon.h
Normal file
46
libraries/iconfix/xdgicon.h
Normal file
@ -0,0 +1,46 @@
|
||||
/* BEGIN_COMMON_COPYRIGHT_HEADER
|
||||
* (c)LGPL2+
|
||||
*
|
||||
* Razor - a lightweight, Qt based, desktop toolset
|
||||
* http://razor-qt.org
|
||||
*
|
||||
* Copyright: 2010-2011 Razor team
|
||||
* Authors:
|
||||
* Alexander Sokoloff <sokoloff.a@gmail.com>
|
||||
*
|
||||
* This program or library 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.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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 library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA
|
||||
*
|
||||
* END_COMMON_COPYRIGHT_HEADER */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtGui/QIcon>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
class XdgIcon
|
||||
{
|
||||
public:
|
||||
static QIcon fromTheme(const QString &iconName, const QIcon &fallback = QIcon());
|
||||
static QIcon fromTheme(const QStringList &iconNames, const QIcon &fallback = QIcon());
|
||||
|
||||
static QString themeName();
|
||||
static void setThemeName(const QString &themeName);
|
||||
|
||||
protected:
|
||||
explicit XdgIcon();
|
||||
virtual ~XdgIcon();
|
||||
};
|
6
libraries/javacheck/.gitignore
vendored
Normal file
6
libraries/javacheck/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
.classpath
|
||||
.idea
|
||||
.project
|
13
libraries/javacheck/CMakeLists.txt
Normal file
13
libraries/javacheck/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
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})
|
24
libraries/javacheck/JavaCheck.java
Normal file
24
libraries/javacheck/JavaCheck.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
6
libraries/launcher/.gitignore
vendored
Normal file
6
libraries/launcher/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
.classpath
|
||||
.idea
|
||||
.project
|
34
libraries/launcher/CMakeLists.txt
Normal file
34
libraries/launcher/CMakeLists.txt
Normal file
@ -0,0 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
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/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})
|
||||
|
165
libraries/launcher/net/minecraft/Launcher.java
Normal file
165
libraries/launcher/net/minecraft/Launcher.java
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
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() {
|
||||
try {
|
||||
return new URL("http://www.minecraft.net/game/");
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getDocumentBase()
|
||||
{
|
||||
try {
|
||||
return new URL("http://www.minecraft.net/game/");
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean b)
|
||||
{
|
||||
super.setVisible(b);
|
||||
wrappedApplet.setVisible(b);
|
||||
}
|
||||
public void update(Graphics paramGraphics)
|
||||
{
|
||||
}
|
||||
public void paint(Graphics paramGraphics)
|
||||
{
|
||||
}
|
||||
}
|
178
libraries/launcher/org/multimc/EntryPoint.java
Normal file
178
libraries/launcher/org/multimc/EntryPoint.java
Normal file
@ -0,0 +1,178 @@
|
||||
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,
|
||||
Abort
|
||||
}
|
||||
|
||||
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 == 1)
|
||||
{
|
||||
String command = pair[0];
|
||||
if (pair[0].equals("launch"))
|
||||
return Action.Launch;
|
||||
|
||||
else if (pair[0].equals("abort"))
|
||||
return Action.Abort;
|
||||
|
||||
else throw new ParseException();
|
||||
}
|
||||
|
||||
if(pair.length != 2)
|
||||
throw new ParseException();
|
||||
|
||||
String command = pair[0];
|
||||
String param = pair[1];
|
||||
|
||||
if(command.equals("launcher"))
|
||||
{
|
||||
if(param.equals("legacy"))
|
||||
{
|
||||
m_launcher = new LegacyLauncher();
|
||||
Utils.log("Using legacy launcher.");
|
||||
Utils.log();
|
||||
return Action.Proceed;
|
||||
}
|
||||
if(param.equals("onesix"))
|
||||
{
|
||||
m_launcher = new OneSixLauncher();
|
||||
Utils.log("Using onesix launcher.");
|
||||
Utils.log();
|
||||
return Action.Proceed;
|
||||
}
|
||||
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;
|
||||
boolean isAborted = false;
|
||||
// Main loop
|
||||
while (isListening)
|
||||
{
|
||||
String inData;
|
||||
try
|
||||
{
|
||||
// Read from the pipe one line at a time
|
||||
inData = buffer.readLine();
|
||||
if (inData != null)
|
||||
{
|
||||
Action a = parseLine(inData);
|
||||
if(a == Action.Abort)
|
||||
{
|
||||
isListening = false;
|
||||
isAborted = true;
|
||||
}
|
||||
if(a == Action.Launch)
|
||||
{
|
||||
isListening = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isListening = false;
|
||||
isAborted = true;
|
||||
}
|
||||
}
|
||||
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(isAborted)
|
||||
{
|
||||
System.err.println("Launch aborted by MultiMC.");
|
||||
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;
|
||||
}
|
132
libraries/launcher/org/multimc/IconLoader.java
Normal file
132
libraries/launcher/org/multimc/IconLoader.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
22
libraries/launcher/org/multimc/Launcher.java
Normal file
22
libraries/launcher/org/multimc/Launcher.java
Normal file
@ -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);
|
||||
}
|
112
libraries/launcher/org/multimc/LegacyFrame.java
Normal file
112
libraries/launcher/org/multimc/LegacyFrame.java
Normal file
@ -0,0 +1,112 @@
|
||||
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 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 ) {}
|
||||
}
|
21
libraries/launcher/org/multimc/NotFoundException.java
Normal file
21
libraries/launcher/org/multimc/NotFoundException.java
Normal file
@ -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
|
||||
{
|
||||
}
|
86
libraries/launcher/org/multimc/ParamBucket.java
Normal file
86
libraries/launcher/org/multimc/ParamBucket.java
Normal file
@ -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>>();
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
/* Copyright 2015 MultiMC Contributors
|
||||
/*
|
||||
* 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.
|
||||
@ -13,19 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
package org.multimc;
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QUrl;
|
||||
class QString;
|
||||
class QDir;
|
||||
|
||||
namespace Wonko
|
||||
public class ParseException extends java.lang.Exception
|
||||
{
|
||||
MULTIMC_LOGIC_EXPORT QUrl rootUrl();
|
||||
MULTIMC_LOGIC_EXPORT QUrl indexUrl();
|
||||
MULTIMC_LOGIC_EXPORT QUrl versionListUrl(const QString &uid);
|
||||
MULTIMC_LOGIC_EXPORT QUrl versionUrl(const QString &uid, const QString &version);
|
||||
MULTIMC_LOGIC_EXPORT QDir localWonkoDir();
|
||||
|
||||
}
|
280
libraries/launcher/org/multimc/Utils.java
Normal file
280
libraries/launcher/org/multimc/Utils.java
Normal file
@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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.io.File;
|
||||
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.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
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 MinecraftLauncher::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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a 'target' string 'suffix' with 'replacement'
|
||||
*/
|
||||
public static String replaceSuffix (String target, String suffix, String replacement)
|
||||
{
|
||||
if (!target.endsWith(suffix))
|
||||
{
|
||||
return target;
|
||||
}
|
||||
String prefix = target.substring(0, target.length() - suffix.length());
|
||||
return prefix + replacement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzip zip file with natives 'source' into the folder 'targetFolder'
|
||||
*
|
||||
* Contains a hack for OSX. Yay.
|
||||
* @param source
|
||||
* @param targetFolder
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void unzipNatives(File source, File targetFolder) throws IOException
|
||||
{
|
||||
ZipFile zip = new ZipFile(source);
|
||||
|
||||
boolean applyHacks = false;
|
||||
String[] javaVersionElements = System.getProperty("java.version").split("[.\\-+]");
|
||||
int major = Integer.parseInt(javaVersionElements[0]);
|
||||
if(major == 1)
|
||||
{
|
||||
major = Integer.parseInt(javaVersionElements[1]);
|
||||
}
|
||||
if (major >= 8)
|
||||
{
|
||||
applyHacks = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Enumeration entries = zip.entries();
|
||||
|
||||
while (entries.hasMoreElements())
|
||||
{
|
||||
ZipEntry entry = (ZipEntry) entries.nextElement();
|
||||
|
||||
String entryName = entry.getName();
|
||||
String fileName = entryName;
|
||||
if(applyHacks)
|
||||
{
|
||||
fileName = replaceSuffix(entryName, ".jnilib", ".dylib");
|
||||
}
|
||||
File targetFile = new File(targetFolder, fileName);
|
||||
if (targetFile.getParentFile() != null)
|
||||
{
|
||||
targetFile.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
if (entry.isDirectory())
|
||||
continue;
|
||||
|
||||
copyStream(zip.getInputStream(entry), new BufferedOutputStream(new FileOutputStream(targetFile)));
|
||||
}
|
||||
} finally
|
||||
{
|
||||
zip.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
175
libraries/launcher/org/multimc/legacy/LegacyLauncher.java
Normal file
175
libraries/launcher/org/multimc/legacy/LegacyLauncher.java
Normal file
@ -0,0 +1,175 @@
|
||||
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.*;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
367
libraries/launcher/org/multimc/onesix/OneSixLauncher.java
Normal file
367
libraries/launcher/org/multimc/onesix/OneSixLauncher.java
Normal file
@ -0,0 +1,367 @@
|
||||
/* 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.applet.Applet;
|
||||
import java.io.File;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class OneSixLauncher implements Launcher
|
||||
{
|
||||
// parameters, separated from ParamBucket
|
||||
private List<String> libraries;
|
||||
private List<String> extlibs;
|
||||
private List<String> extlibs32;
|
||||
private List<String> extlibs64;
|
||||
private List<String> mcparams;
|
||||
private List<String> mods;
|
||||
private List<String> jarmods;
|
||||
private List<String> coremods;
|
||||
private List<String> traits;
|
||||
private String appletClass;
|
||||
private String mainClass;
|
||||
private String nativePath;
|
||||
private String userName, sessionId;
|
||||
private String windowTitle;
|
||||
private String windowParams;
|
||||
|
||||
// secondary parameters
|
||||
private Dimension winSize;
|
||||
private boolean maximize;
|
||||
private String cwd;
|
||||
|
||||
// the much abused system classloader, for convenience (for further abuse)
|
||||
private ClassLoader cl;
|
||||
|
||||
private void processParams(ParamBucket params) throws NotFoundException
|
||||
{
|
||||
libraries = params.all("cp");
|
||||
extlibs = params.allSafe("ext", new ArrayList<String>());
|
||||
extlibs32 = params.allSafe("ext32", new ArrayList<String>());
|
||||
extlibs64 = params.allSafe("ext64", new ArrayList<String>());
|
||||
|
||||
// Unify the extracted native libs according to actual system architecture
|
||||
String property = System.getProperty("os.arch");
|
||||
boolean is_64 = property.equalsIgnoreCase("x86_64") || property.equalsIgnoreCase("amd64");
|
||||
if(is_64)
|
||||
{
|
||||
extlibs.addAll(extlibs64);
|
||||
}
|
||||
else
|
||||
{
|
||||
extlibs.addAll(extlibs32);
|
||||
}
|
||||
|
||||
mcparams = params.allSafe("param", new ArrayList<String>() );
|
||||
mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft");
|
||||
appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet");
|
||||
mods = params.allSafe("mod", new ArrayList<String>());
|
||||
jarmods = params.allSafe("jarmod", new ArrayList<String>());
|
||||
coremods = params.allSafe("coremod", new ArrayList<String>());
|
||||
traits = params.allSafe("traits", new ArrayList<String>());
|
||||
nativePath = params.first("natives");
|
||||
|
||||
userName = params.first("userName");
|
||||
sessionId = params.first("sessionId");
|
||||
windowTitle = params.firstSafe("windowTitle", "Minecraft");
|
||||
windowParams = params.firstSafe("windowParams", "854x480");
|
||||
|
||||
cwd = System.getProperty("user.dir");
|
||||
winSize = new Dimension(854, 480);
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
|
||||
private void printStats()
|
||||
{
|
||||
Utils.log("Main Class:");
|
||||
Utils.log(" " + mainClass);
|
||||
Utils.log();
|
||||
|
||||
Utils.log("Native path:");
|
||||
Utils.log(" " + nativePath);
|
||||
Utils.log();
|
||||
|
||||
Utils.log("Traits:");
|
||||
Utils.log(" " + traits);
|
||||
Utils.log();
|
||||
|
||||
Utils.log("Libraries:");
|
||||
for (String s : libraries)
|
||||
{
|
||||
File f = new File(s);
|
||||
if (f.exists())
|
||||
{
|
||||
Utils.log(" " + s);
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.log(" " + s + " (missing)", "Warning");
|
||||
}
|
||||
}
|
||||
Utils.log();
|
||||
|
||||
if(mods.size() > 0)
|
||||
{
|
||||
Utils.log("Mods:");
|
||||
for (String s : mods)
|
||||
{
|
||||
Utils.log(" " + s);
|
||||
}
|
||||
Utils.log();
|
||||
}
|
||||
|
||||
if(coremods.size() > 0)
|
||||
{
|
||||
Utils.log("Core Mods:");
|
||||
for (String s : coremods)
|
||||
{
|
||||
Utils.log(" " + s);
|
||||
}
|
||||
Utils.log();
|
||||
}
|
||||
|
||||
if(jarmods.size() > 0)
|
||||
{
|
||||
Utils.log("Jar Mods:");
|
||||
for (String s : jarmods)
|
||||
{
|
||||
Utils.log(" " + s);
|
||||
}
|
||||
Utils.log();
|
||||
}
|
||||
|
||||
Utils.log("Params:");
|
||||
Utils.log(" " + mcparams.toString());
|
||||
Utils.log();
|
||||
if(maximize)
|
||||
Utils.log("Window size: max (if available)");
|
||||
else
|
||||
Utils.log("Window size: " + Integer.toString(winSize.width) + " x " + Integer.toString(winSize.height));
|
||||
Utils.log();
|
||||
}
|
||||
|
||||
int legacyLaunch()
|
||||
{
|
||||
// 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.");
|
||||
}
|
||||
else
|
||||
{
|
||||
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(appletClass);
|
||||
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;
|
||||
}
|
||||
|
||||
int launchWithMainClass()
|
||||
{
|
||||
// window size, title and state, onesix
|
||||
if (maximize)
|
||||
{
|
||||
// FIXME: there is no good way to maximize the minecraft window in onesix.
|
||||
// the following often breaks linux screen setups
|
||||
// mcparams.add("--fullscreen");
|
||||
}
|
||||
else
|
||||
{
|
||||
mcparams.add("--width");
|
||||
mcparams.add(Integer.toString(winSize.width));
|
||||
mcparams.add("--height");
|
||||
mcparams.add(Integer.toString(winSize.height));
|
||||
}
|
||||
|
||||
System.setProperty("minecraft.applet.TargetDirectory", cwd);
|
||||
|
||||
// Get the Minecraft Class.
|
||||
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;
|
||||
}
|
||||
// init params for the main method to chomp on.
|
||||
String[] paramsArray = mcparams.toArray(new String[mcparams.size()]);
|
||||
try
|
||||
{
|
||||
// static method doesn't have an instance
|
||||
meth.invoke(null, (Object) paramsArray);
|
||||
} catch (Exception e)
|
||||
{
|
||||
System.err.println("Failed to start Minecraft:");
|
||||
e.printStackTrace(System.err);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int launch(ParamBucket params)
|
||||
{
|
||||
// get and process the launch script params
|
||||
try
|
||||
{
|
||||
processParams(params);
|
||||
} catch (NotFoundException e)
|
||||
{
|
||||
System.err.println("Not enough arguments.");
|
||||
e.printStackTrace(System.err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// add libraries to classpath
|
||||
if(!Utils.addToClassPath(libraries))
|
||||
{
|
||||
System.err.println("Halting launch due to previous errors.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// print the pretty things
|
||||
printStats();
|
||||
|
||||
// extract native libs (depending on platform here... java!)
|
||||
Utils.log("Preparing native libraries...");
|
||||
for(String extlib: extlibs)
|
||||
{
|
||||
try
|
||||
{
|
||||
File extlibf = new File(extlib);
|
||||
Utils.log("Extracting " + extlibf.getName());
|
||||
Utils.unzipNatives(extlibf, new File(nativePath));
|
||||
} catch (IOException e)
|
||||
{
|
||||
System.err.println("Failed to extract native library:");
|
||||
e.printStackTrace(System.err);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Utils.log();
|
||||
|
||||
// set the native libs path... the brute force way
|
||||
try
|
||||
{
|
||||
System.setProperty("java.library.path", nativePath);
|
||||
System.setProperty("org.lwjgl.librarypath", nativePath);
|
||||
System.setProperty("net.java.games.input.librarypath", nativePath);
|
||||
// by the power of reflection, initialize native libs again. DIRTY!
|
||||
// this is SO BAD. imagine doing that to ld
|
||||
Field 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);
|
||||
System.err.println("Minecraft might fail to launch...");
|
||||
}
|
||||
|
||||
// grab the system classloader and ...
|
||||
cl = ClassLoader.getSystemClassLoader();
|
||||
|
||||
if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") )
|
||||
{
|
||||
// legacy launch uses the applet wrapper
|
||||
return legacyLaunch();
|
||||
}
|
||||
else
|
||||
{
|
||||
// normal launch just calls main()
|
||||
return launchWithMainClass();
|
||||
}
|
||||
}
|
||||
}
|
176
libraries/launcher/org/simplericity/macify/eawt/Application.java
Normal file
176
libraries/launcher/org/simplericity/macify/eawt/Application.java
Normal file
@ -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();
|
||||
|
||||
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
1
libraries/libnbtplusplus
Submodule
1
libraries/libnbtplusplus
Submodule
Submodule libraries/libnbtplusplus added at 5d0ffb50a5
@ -1,133 +0,0 @@
|
||||
/* Copyright 2015 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 "AbstractCommonModel.h"
|
||||
|
||||
BaseAbstractCommonModel::BaseAbstractCommonModel(const Qt::Orientation orientation, QObject *parent)
|
||||
: QAbstractListModel(parent), m_orientation(orientation)
|
||||
{
|
||||
}
|
||||
|
||||
int BaseAbstractCommonModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return m_orientation == Qt::Horizontal ? entryCount() : size();
|
||||
}
|
||||
int BaseAbstractCommonModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return m_orientation == Qt::Horizontal ? size() : entryCount();
|
||||
}
|
||||
QVariant BaseAbstractCommonModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!hasIndex(index.row(), index.column(), index.parent()))
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
const int i = m_orientation == Qt::Horizontal ? index.column() : index.row();
|
||||
const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column();
|
||||
return formatData(i, role, get(i, entry, role));
|
||||
}
|
||||
QVariant BaseAbstractCommonModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation != m_orientation && role == Qt::DisplayRole)
|
||||
{
|
||||
return entryTitle(section);
|
||||
}
|
||||
else
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
bool BaseAbstractCommonModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
const int i = m_orientation == Qt::Horizontal ? index.column() : index.row();
|
||||
const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column();
|
||||
const bool result = set(i, entry, role, sanetizeData(i, role, value));
|
||||
if (result)
|
||||
{
|
||||
emit dataChanged(index, index, QVector<int>() << role);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Qt::ItemFlags BaseAbstractCommonModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!hasIndex(index.row(), index.column(), index.parent()))
|
||||
{
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column();
|
||||
if (canSet(entry))
|
||||
{
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
}
|
||||
|
||||
void BaseAbstractCommonModel::notifyAboutToAddObject(const int at)
|
||||
{
|
||||
if (m_orientation == Qt::Horizontal)
|
||||
{
|
||||
beginInsertColumns(QModelIndex(), at, at);
|
||||
}
|
||||
else
|
||||
{
|
||||
beginInsertRows(QModelIndex(), at, at);
|
||||
}
|
||||
}
|
||||
void BaseAbstractCommonModel::notifyObjectAdded()
|
||||
{
|
||||
if (m_orientation == Qt::Horizontal)
|
||||
{
|
||||
endInsertColumns();
|
||||
}
|
||||
else
|
||||
{
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
void BaseAbstractCommonModel::notifyAboutToRemoveObject(const int at)
|
||||
{
|
||||
if (m_orientation == Qt::Horizontal)
|
||||
{
|
||||
beginRemoveColumns(QModelIndex(), at, at);
|
||||
}
|
||||
else
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), at, at);
|
||||
}
|
||||
}
|
||||
void BaseAbstractCommonModel::notifyObjectRemoved()
|
||||
{
|
||||
if (m_orientation == Qt::Horizontal)
|
||||
{
|
||||
endRemoveColumns();
|
||||
}
|
||||
else
|
||||
{
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
void BaseAbstractCommonModel::notifyBeginReset()
|
||||
{
|
||||
beginResetModel();
|
||||
}
|
||||
void BaseAbstractCommonModel::notifyEndReset()
|
||||
{
|
||||
endResetModel();
|
||||
}
|
@ -1,462 +0,0 @@
|
||||
/* Copyright 2015 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 <QAbstractListModel>
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
class BaseAbstractCommonModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BaseAbstractCommonModel(const Qt::Orientation orientation, QObject *parent = nullptr);
|
||||
|
||||
// begin QAbstractItemModel interface
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
// end QAbstractItemModel interface
|
||||
|
||||
virtual int size() const = 0;
|
||||
virtual int entryCount() const = 0;
|
||||
|
||||
virtual QVariant formatData(const int index, int role, const QVariant &data) const { return data; }
|
||||
virtual QVariant sanetizeData(const int index, int role, const QVariant &data) const { return data; }
|
||||
|
||||
protected:
|
||||
virtual QVariant get(const int index, const int entry, const int role) const = 0;
|
||||
virtual bool set(const int index, const int entry, const int role, const QVariant &value) = 0;
|
||||
virtual bool canSet(const int entry) const = 0;
|
||||
virtual QString entryTitle(const int entry) const = 0;
|
||||
|
||||
void notifyAboutToAddObject(const int at);
|
||||
void notifyObjectAdded();
|
||||
void notifyAboutToRemoveObject(const int at);
|
||||
void notifyObjectRemoved();
|
||||
void notifyBeginReset();
|
||||
void notifyEndReset();
|
||||
|
||||
const Qt::Orientation m_orientation;
|
||||
};
|
||||
|
||||
template<typename Object>
|
||||
class AbstractCommonModel : public BaseAbstractCommonModel
|
||||
{
|
||||
public:
|
||||
explicit AbstractCommonModel(const Qt::Orientation orientation)
|
||||
: BaseAbstractCommonModel(orientation) {}
|
||||
virtual ~AbstractCommonModel() {}
|
||||
|
||||
int size() const override { return m_objects.size(); }
|
||||
int entryCount() const override { return m_entries.size(); }
|
||||
|
||||
void append(const Object &object)
|
||||
{
|
||||
notifyAboutToAddObject(size());
|
||||
m_objects.append(object);
|
||||
notifyObjectAdded();
|
||||
}
|
||||
void prepend(const Object &object)
|
||||
{
|
||||
notifyAboutToAddObject(0);
|
||||
m_objects.prepend(object);
|
||||
notifyObjectAdded();
|
||||
}
|
||||
void insert(const Object &object, const int index)
|
||||
{
|
||||
if (index >= size())
|
||||
{
|
||||
prepend(object);
|
||||
}
|
||||
else if (index <= 0)
|
||||
{
|
||||
append(object);
|
||||
}
|
||||
else
|
||||
{
|
||||
notifyAboutToAddObject(index);
|
||||
m_objects.insert(index, object);
|
||||
notifyObjectAdded();
|
||||
}
|
||||
}
|
||||
void remove(const int index)
|
||||
{
|
||||
notifyAboutToRemoveObject(index);
|
||||
m_objects.removeAt(index);
|
||||
notifyObjectRemoved();
|
||||
}
|
||||
Object get(const int index) const
|
||||
{
|
||||
return m_objects.at(index);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class CommonModel;
|
||||
QVariant get(const int index, const int entry, const int role) const override
|
||||
{
|
||||
if (m_entries.size() < entry || !m_entries[entry].second.contains(role))
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
return m_entries[entry].second.value(role)->get(m_objects.at(index));
|
||||
}
|
||||
bool set(const int index, const int entry, const int role, const QVariant &value) override
|
||||
{
|
||||
if (m_entries.size() < entry || !m_entries[entry].second.contains(role))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
IEntry *e = m_entries[entry].second.value(role);
|
||||
if (!e->canSet())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
e->set(m_objects[index], value);
|
||||
return true;
|
||||
}
|
||||
bool canSet(const int entry) const override
|
||||
{
|
||||
if (m_entries.size() < entry || !m_entries[entry].second.contains(Qt::EditRole))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
IEntry *e = m_entries[entry].second.value(Qt::EditRole);
|
||||
return e->canSet();
|
||||
}
|
||||
|
||||
QString entryTitle(const int entry) const override
|
||||
{
|
||||
return m_entries.at(entry).first;
|
||||
}
|
||||
|
||||
private:
|
||||
struct IEntry
|
||||
{
|
||||
virtual ~IEntry() {}
|
||||
virtual void set(Object &object, const QVariant &value) = 0;
|
||||
virtual QVariant get(const Object &object) const = 0;
|
||||
virtual bool canSet() const = 0;
|
||||
};
|
||||
template<typename T>
|
||||
struct VariableEntry : public IEntry
|
||||
{
|
||||
typedef T (Object::*Member);
|
||||
|
||||
explicit VariableEntry(Member member)
|
||||
: m_member(member) {}
|
||||
|
||||
void set(Object &object, const QVariant &value) override
|
||||
{
|
||||
object.*m_member = value.value<T>();
|
||||
}
|
||||
QVariant get(const Object &object) const override
|
||||
{
|
||||
return QVariant::fromValue<T>(object.*m_member);
|
||||
}
|
||||
bool canSet() const override { return true; }
|
||||
|
||||
private:
|
||||
Member m_member;
|
||||
};
|
||||
template<typename T>
|
||||
struct FunctionEntry : public IEntry
|
||||
{
|
||||
typedef T (Object::*Getter)() const;
|
||||
typedef void (Object::*Setter)(T);
|
||||
|
||||
explicit FunctionEntry(Getter getter, Setter setter)
|
||||
: m_getter(m_getter), m_setter(m_setter) {}
|
||||
|
||||
void set(Object &object, const QVariant &value) override
|
||||
{
|
||||
object.*m_setter(value.value<T>());
|
||||
}
|
||||
QVariant get(const Object &object) const override
|
||||
{
|
||||
return QVariant::fromValue<T>(object.*m_getter());
|
||||
}
|
||||
bool canSet() const override { return !!m_setter; }
|
||||
|
||||
private:
|
||||
Getter m_getter;
|
||||
Setter m_setter;
|
||||
};
|
||||
|
||||
QList<Object> m_objects;
|
||||
QVector<QPair<QString, QMap<int, IEntry *>>> m_entries;
|
||||
|
||||
void addEntryInternal(IEntry *e, const int entry, const int role)
|
||||
{
|
||||
if (m_entries.size() <= entry)
|
||||
{
|
||||
m_entries.resize(entry + 1);
|
||||
}
|
||||
m_entries[entry].second.insert(role, e);
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename Getter, typename Setter>
|
||||
typename std::enable_if<std::is_member_function_pointer<Getter>::value && std::is_member_function_pointer<Getter>::value, void>::type
|
||||
addEntry(Getter getter, Setter setter, const int entry, const int role)
|
||||
{
|
||||
addEntryInternal(new FunctionEntry<typename std::result_of<Getter>::type>(getter, setter), entry, role);
|
||||
}
|
||||
template<typename Getter>
|
||||
typename std::enable_if<std::is_member_function_pointer<Getter>::value, void>::type
|
||||
addEntry(Getter getter, const int entry, const int role)
|
||||
{
|
||||
addEntryInternal(new FunctionEntry<typename std::result_of<Getter>::type>(getter, nullptr), entry, role);
|
||||
}
|
||||
template<typename T>
|
||||
typename std::enable_if<!std::is_member_function_pointer<T (Object::*)>::value, void>::type
|
||||
addEntry(T (Object::*member), const int entry, const int role)
|
||||
{
|
||||
addEntryInternal(new VariableEntry<T>(member), entry, role);
|
||||
}
|
||||
|
||||
void setEntryTitle(const int entry, const QString &title)
|
||||
{
|
||||
m_entries[entry].first = title;
|
||||
}
|
||||
};
|
||||
template<typename Object>
|
||||
class AbstractCommonModel<Object *> : public BaseAbstractCommonModel
|
||||
{
|
||||
public:
|
||||
explicit AbstractCommonModel(const Qt::Orientation orientation)
|
||||
: BaseAbstractCommonModel(orientation) {}
|
||||
virtual ~AbstractCommonModel()
|
||||
{
|
||||
qDeleteAll(m_objects);
|
||||
}
|
||||
|
||||
int size() const override { return m_objects.size(); }
|
||||
int entryCount() const override { return m_entries.size(); }
|
||||
|
||||
void append(Object *object)
|
||||
{
|
||||
notifyAboutToAddObject(size());
|
||||
m_objects.append(object);
|
||||
notifyObjectAdded();
|
||||
}
|
||||
void prepend(Object *object)
|
||||
{
|
||||
notifyAboutToAddObject(0);
|
||||
m_objects.prepend(object);
|
||||
notifyObjectAdded();
|
||||
}
|
||||
void insert(Object *object, const int index)
|
||||
{
|
||||
if (index >= size())
|
||||
{
|
||||
prepend(object);
|
||||
}
|
||||
else if (index <= 0)
|
||||
{
|
||||
append(object);
|
||||
}
|
||||
else
|
||||
{
|
||||
notifyAboutToAddObject(index);
|
||||
m_objects.insert(index, object);
|
||||
notifyObjectAdded();
|
||||
}
|
||||
}
|
||||
void remove(const int index)
|
||||
{
|
||||
notifyAboutToRemoveObject(index);
|
||||
m_objects.removeAt(index);
|
||||
notifyObjectRemoved();
|
||||
}
|
||||
Object *get(const int index) const
|
||||
{
|
||||
return m_objects.at(index);
|
||||
}
|
||||
int find(Object * const obj) const
|
||||
{
|
||||
return m_objects.indexOf(obj);
|
||||
}
|
||||
|
||||
QList<Object *> getAll() const
|
||||
{
|
||||
return m_objects;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class CommonModel;
|
||||
QVariant get(const int index, const int entry, const int role) const override
|
||||
{
|
||||
if (m_entries.size() < entry || !m_entries[entry].second.contains(role))
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
return m_entries[entry].second.value(role)->get(m_objects.at(index));
|
||||
}
|
||||
bool set(const int index, const int entry, const int role, const QVariant &value) override
|
||||
{
|
||||
if (m_entries.size() < entry || !m_entries[entry].second.contains(role))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
IEntry *e = m_entries[entry].second.value(role);
|
||||
if (!e->canSet())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
e->set(m_objects[index], value);
|
||||
return true;
|
||||
}
|
||||
bool canSet(const int entry) const override
|
||||
{
|
||||
if (m_entries.size() < entry || !m_entries[entry].second.contains(Qt::EditRole))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
IEntry *e = m_entries[entry].second.value(Qt::EditRole);
|
||||
return e->canSet();
|
||||
}
|
||||
|
||||
QString entryTitle(const int entry) const override
|
||||
{
|
||||
return m_entries.at(entry).first;
|
||||
}
|
||||
|
||||
private:
|
||||
struct IEntry
|
||||
{
|
||||
virtual ~IEntry() {}
|
||||
virtual void set(Object *object, const QVariant &value) = 0;
|
||||
virtual QVariant get(Object *object) const = 0;
|
||||
virtual bool canSet() const = 0;
|
||||
};
|
||||
template<typename T>
|
||||
struct VariableEntry : public IEntry
|
||||
{
|
||||
typedef T (Object::*Member);
|
||||
|
||||
explicit VariableEntry(Member member)
|
||||
: m_member(member) {}
|
||||
|
||||
void set(Object *object, const QVariant &value) override
|
||||
{
|
||||
object->*m_member = value.value<T>();
|
||||
}
|
||||
QVariant get(Object *object) const override
|
||||
{
|
||||
return QVariant::fromValue<T>(object->*m_member);
|
||||
}
|
||||
bool canSet() const override { return true; }
|
||||
|
||||
private:
|
||||
Member m_member;
|
||||
};
|
||||
template<typename T>
|
||||
struct FunctionEntry : public IEntry
|
||||
{
|
||||
typedef T (Object::*Getter)() const;
|
||||
typedef void (Object::*Setter)(T);
|
||||
|
||||
explicit FunctionEntry(Getter getter, Setter setter)
|
||||
: m_getter(getter), m_setter(setter) {}
|
||||
|
||||
void set(Object *object, const QVariant &value) override
|
||||
{
|
||||
(object->*m_setter)(value.value<T>());
|
||||
}
|
||||
QVariant get(Object *object) const override
|
||||
{
|
||||
return QVariant::fromValue<T>((object->*m_getter)());
|
||||
}
|
||||
bool canSet() const override { return !!m_setter; }
|
||||
|
||||
private:
|
||||
Getter m_getter;
|
||||
Setter m_setter;
|
||||
};
|
||||
template<typename T>
|
||||
struct LambdaEntry : public IEntry
|
||||
{
|
||||
using Getter = std::function<T(Object *)>;
|
||||
|
||||
explicit LambdaEntry(Getter getter)
|
||||
: m_getter(getter) {}
|
||||
|
||||
void set(Object *object, const QVariant &value) override {}
|
||||
QVariant get(Object *object) const override
|
||||
{
|
||||
return QVariant::fromValue<T>(m_getter(object));
|
||||
}
|
||||
bool canSet() const override { return false; }
|
||||
|
||||
private:
|
||||
Getter m_getter;
|
||||
};
|
||||
|
||||
QList<Object *> m_objects;
|
||||
QVector<QPair<QString, QMap<int, IEntry *>>> m_entries;
|
||||
|
||||
void addEntryInternal(IEntry *e, const int entry, const int role)
|
||||
{
|
||||
if (m_entries.size() <= entry)
|
||||
{
|
||||
m_entries.resize(entry + 1);
|
||||
}
|
||||
m_entries[entry].second.insert(role, e);
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename Getter, typename Setter>
|
||||
typename std::enable_if<std::is_member_function_pointer<Getter>::value && std::is_member_function_pointer<Getter>::value, void>::type
|
||||
addEntry(const int entry, const int role, Getter getter, Setter setter)
|
||||
{
|
||||
addEntryInternal(new FunctionEntry<typename std::result_of<Getter>::type>(getter, setter), entry, role);
|
||||
}
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_member_function_pointer<typename FunctionEntry<T>::Getter>::value, void>::type
|
||||
addEntry(const int entry, const int role, typename FunctionEntry<T>::Getter getter)
|
||||
{
|
||||
addEntryInternal(new FunctionEntry<T>(getter, nullptr), entry, role);
|
||||
}
|
||||
template<typename T>
|
||||
typename std::enable_if<!std::is_member_function_pointer<T (Object::*)>::value, void>::type
|
||||
addEntry(const int entry, const int role, T (Object::*member))
|
||||
{
|
||||
addEntryInternal(new VariableEntry<T>(member), entry, role);
|
||||
}
|
||||
template<typename T>
|
||||
void addEntry(const int entry, const int role, typename LambdaEntry<T>::Getter lambda)
|
||||
{
|
||||
addEntryInternal(new LambdaEntry<T>(lambda), entry, role);
|
||||
}
|
||||
|
||||
void setEntryTitle(const int entry, const QString &title)
|
||||
{
|
||||
m_entries[entry].first = title;
|
||||
}
|
||||
|
||||
void setAll(const QList<Object *> objects)
|
||||
{
|
||||
notifyBeginReset();
|
||||
qDeleteAll(m_objects);
|
||||
m_objects = objects;
|
||||
notifyEndReset();
|
||||
}
|
||||
};
|
@ -1,103 +0,0 @@
|
||||
/* Copyright 2015 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 "BaseConfigObject.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QFile>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
#include "Exception.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
BaseConfigObject::BaseConfigObject(const QString &filename)
|
||||
: m_filename(filename)
|
||||
{
|
||||
m_saveTimer = new QTimer;
|
||||
m_saveTimer->setSingleShot(true);
|
||||
// cppcheck-suppress pureVirtualCall
|
||||
QObject::connect(m_saveTimer, &QTimer::timeout, [this](){saveNow();});
|
||||
setSaveTimeout(250);
|
||||
|
||||
m_initialReadTimer = new QTimer;
|
||||
m_initialReadTimer->setSingleShot(true);
|
||||
QObject::connect(m_initialReadTimer, &QTimer::timeout, [this]()
|
||||
{
|
||||
loadNow();
|
||||
m_initialReadTimer->deleteLater();
|
||||
m_initialReadTimer = 0;
|
||||
});
|
||||
m_initialReadTimer->start(0);
|
||||
|
||||
// cppcheck-suppress pureVirtualCall
|
||||
m_appQuitConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this](){saveNow();});
|
||||
}
|
||||
BaseConfigObject::~BaseConfigObject()
|
||||
{
|
||||
delete m_saveTimer;
|
||||
if (m_initialReadTimer)
|
||||
{
|
||||
delete m_initialReadTimer;
|
||||
}
|
||||
QObject::disconnect(m_appQuitConnection);
|
||||
}
|
||||
|
||||
void BaseConfigObject::setSaveTimeout(int msec)
|
||||
{
|
||||
m_saveTimer->setInterval(msec);
|
||||
}
|
||||
|
||||
void BaseConfigObject::scheduleSave()
|
||||
{
|
||||
m_saveTimer->stop();
|
||||
m_saveTimer->start();
|
||||
}
|
||||
void BaseConfigObject::saveNow()
|
||||
{
|
||||
if (m_saveTimer->isActive())
|
||||
{
|
||||
m_saveTimer->stop();
|
||||
}
|
||||
if (m_disableSaving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FS::write(m_filename, doSave());
|
||||
}
|
||||
catch (Exception & e)
|
||||
{
|
||||
qCritical() << e.cause();
|
||||
}
|
||||
}
|
||||
void BaseConfigObject::loadNow()
|
||||
{
|
||||
if (m_saveTimer->isActive())
|
||||
{
|
||||
saveNow();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
doLoad(FS::read(m_filename));
|
||||
}
|
||||
catch (Exception & e)
|
||||
{
|
||||
qWarning() << "Error loading" << m_filename << ":" << e.cause();
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/* Copyright 2015 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>
|
||||
|
||||
class QTimer;
|
||||
|
||||
class BaseConfigObject
|
||||
{
|
||||
public:
|
||||
void setSaveTimeout(int msec);
|
||||
|
||||
protected:
|
||||
explicit BaseConfigObject(const QString &filename);
|
||||
virtual ~BaseConfigObject();
|
||||
|
||||
// cppcheck-suppress pureVirtualCall
|
||||
virtual QByteArray doSave() const = 0;
|
||||
virtual void doLoad(const QByteArray &data) = 0;
|
||||
|
||||
void setSavingDisabled(bool savingDisabled) { m_disableSaving = savingDisabled; }
|
||||
|
||||
QString fileName() const { return m_filename; }
|
||||
|
||||
public:
|
||||
void scheduleSave();
|
||||
void saveNow();
|
||||
void loadNow();
|
||||
|
||||
private:
|
||||
QTimer *m_saveTimer;
|
||||
QTimer *m_initialReadTimer;
|
||||
QString m_filename;
|
||||
QMetaObject::Connection m_appQuitConnection;
|
||||
bool m_disableSaving = false;
|
||||
};
|
@ -1,61 +0,0 @@
|
||||
/* Copyright 2013-2015 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 <QFile>
|
||||
|
||||
#include "BaseInstaller.h"
|
||||
#include "minecraft/onesix/OneSixInstance.h"
|
||||
|
||||
BaseInstaller::BaseInstaller()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool BaseInstaller::isApplied(OneSixInstance *on)
|
||||
{
|
||||
return QFile::exists(filename(on->instanceRoot()));
|
||||
}
|
||||
|
||||
bool BaseInstaller::add(OneSixInstance *to)
|
||||
{
|
||||
if (!patchesDir(to->instanceRoot()).exists())
|
||||
{
|
||||
QDir(to->instanceRoot()).mkdir("patches");
|
||||
}
|
||||
|
||||
if (isApplied(to))
|
||||
{
|
||||
if (!remove(to))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseInstaller::remove(OneSixInstance *from)
|
||||
{
|
||||
return QFile::remove(filename(from->instanceRoot()));
|
||||
}
|
||||
|
||||
QString BaseInstaller::filename(const QString &root) const
|
||||
{
|
||||
return patchesDir(root).absoluteFilePath(id() + ".json");
|
||||
}
|
||||
QDir BaseInstaller::patchesDir(const QString &root) const
|
||||
{
|
||||
return QDir(root + "/patches/");
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/* Copyright 2013-2015 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 <memory>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class OneSixInstance;
|
||||
class QDir;
|
||||
class QString;
|
||||
class QObject;
|
||||
class Task;
|
||||
class BaseVersion;
|
||||
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT BaseInstaller
|
||||
{
|
||||
public:
|
||||
BaseInstaller();
|
||||
virtual ~BaseInstaller(){};
|
||||
bool isApplied(OneSixInstance *on);
|
||||
|
||||
virtual bool add(OneSixInstance *to);
|
||||
virtual bool remove(OneSixInstance *from);
|
||||
|
||||
virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
|
||||
|
||||
protected:
|
||||
virtual QString id() const = 0;
|
||||
QString filename(const QString &root) const;
|
||||
QDir patchesDir(const QString &root) const;
|
||||
};
|
@ -1,270 +0,0 @@
|
||||
/* Copyright 2013-2015 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 "BaseInstance.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "settings/Setting.h"
|
||||
#include "settings/OverrideSetting.h"
|
||||
|
||||
#include "minecraft/MinecraftVersionList.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Commandline.h"
|
||||
|
||||
BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
|
||||
: QObject()
|
||||
{
|
||||
m_settings = settings;
|
||||
m_rootDir = rootDir;
|
||||
|
||||
m_settings->registerSetting("name", "Unnamed Instance");
|
||||
m_settings->registerSetting("iconKey", "default");
|
||||
m_settings->registerSetting("notes", "");
|
||||
m_settings->registerSetting("lastLaunchTime", 0);
|
||||
m_settings->registerSetting("totalTimePlayed", 0);
|
||||
|
||||
// Custom Commands
|
||||
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
|
||||
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting);
|
||||
|
||||
// Console
|
||||
auto consoleSetting = m_settings->registerSetting("OverrideConsole", false);
|
||||
m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting);
|
||||
}
|
||||
|
||||
QString BaseInstance::getPreLaunchCommand()
|
||||
{
|
||||
return settings()->get("PreLaunchCommand").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::getWrapperCommand()
|
||||
{
|
||||
return settings()->get("WrapperCommand").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::getPostExitCommand()
|
||||
{
|
||||
return settings()->get("PostExitCommand").toString();
|
||||
}
|
||||
|
||||
void BaseInstance::iconUpdated(QString key)
|
||||
{
|
||||
if(iconKey() == key)
|
||||
{
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseInstance::nuke()
|
||||
{
|
||||
FS::deletePath(instanceRoot());
|
||||
emit nuked(this);
|
||||
}
|
||||
|
||||
QString BaseInstance::id() const
|
||||
{
|
||||
return QFileInfo(instanceRoot()).fileName();
|
||||
}
|
||||
|
||||
bool BaseInstance::isRunning() const
|
||||
{
|
||||
return m_isRunning;
|
||||
}
|
||||
|
||||
void BaseInstance::setRunning(bool running)
|
||||
{
|
||||
if(running && !m_isRunning)
|
||||
{
|
||||
m_timeStarted = QDateTime::currentDateTime();
|
||||
}
|
||||
else if(!running && m_isRunning)
|
||||
{
|
||||
qint64 current = settings()->get("totalTimePlayed").toLongLong();
|
||||
QDateTime timeEnded = QDateTime::currentDateTime();
|
||||
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
m_isRunning = running;
|
||||
}
|
||||
|
||||
int64_t BaseInstance::totalTimePlayed() const
|
||||
{
|
||||
qint64 current = settings()->get("totalTimePlayed").toLongLong();
|
||||
if(m_isRunning)
|
||||
{
|
||||
QDateTime timeNow = QDateTime::currentDateTime();
|
||||
return current + m_timeStarted.secsTo(timeNow);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
void BaseInstance::resetTimePlayed()
|
||||
{
|
||||
settings()->reset("totalTimePlayed");
|
||||
}
|
||||
|
||||
QString BaseInstance::instanceType() const
|
||||
{
|
||||
return m_settings->get("InstanceType").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::instanceRoot() const
|
||||
{
|
||||
return m_rootDir;
|
||||
}
|
||||
|
||||
InstancePtr BaseInstance::getSharedPtr()
|
||||
{
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
SettingsObjectPtr BaseInstance::settings() const
|
||||
{
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
BaseInstance::InstanceFlags BaseInstance::flags() const
|
||||
{
|
||||
return m_flags;
|
||||
}
|
||||
|
||||
void BaseInstance::setFlags(const InstanceFlags &flags)
|
||||
{
|
||||
if (flags != m_flags)
|
||||
{
|
||||
m_flags = flags;
|
||||
emit flagsChanged();
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseInstance::setFlag(const BaseInstance::InstanceFlag flag)
|
||||
{
|
||||
// nothing to set?
|
||||
if(flag & m_flags)
|
||||
return;
|
||||
m_flags |= flag;
|
||||
emit flagsChanged();
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
void BaseInstance::unsetFlag(const BaseInstance::InstanceFlag flag)
|
||||
{
|
||||
// nothing to unset?
|
||||
if(!(flag & m_flags))
|
||||
return;
|
||||
m_flags &= ~flag;
|
||||
emit flagsChanged();
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
bool BaseInstance::canLaunch() const
|
||||
{
|
||||
return !(flags() & VersionBrokenFlag);
|
||||
}
|
||||
|
||||
bool BaseInstance::reload()
|
||||
{
|
||||
return m_settings->reload();
|
||||
}
|
||||
|
||||
qint64 BaseInstance::lastLaunch() const
|
||||
{
|
||||
return m_settings->get("lastLaunchTime").value<qint64>();
|
||||
}
|
||||
|
||||
void BaseInstance::setLastLaunch(qint64 val)
|
||||
{
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("lastLaunchTime", val);
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
void BaseInstance::setGroupInitial(QString val)
|
||||
{
|
||||
if(m_group == val)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_group = val;
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
void BaseInstance::setGroupPost(QString val)
|
||||
{
|
||||
if(m_group == val)
|
||||
{
|
||||
return;
|
||||
}
|
||||
setGroupInitial(val);
|
||||
emit groupChanged();
|
||||
}
|
||||
|
||||
QString BaseInstance::group() const
|
||||
{
|
||||
return m_group;
|
||||
}
|
||||
|
||||
void BaseInstance::setNotes(QString val)
|
||||
{
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("notes", val);
|
||||
}
|
||||
|
||||
QString BaseInstance::notes() const
|
||||
{
|
||||
return m_settings->get("notes").toString();
|
||||
}
|
||||
|
||||
void BaseInstance::setIconKey(QString val)
|
||||
{
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("iconKey", val);
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
QString BaseInstance::iconKey() const
|
||||
{
|
||||
return m_settings->get("iconKey").toString();
|
||||
}
|
||||
|
||||
void BaseInstance::setName(QString val)
|
||||
{
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("name", val);
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
QString BaseInstance::name() const
|
||||
{
|
||||
return m_settings->get("name").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::windowTitle() const
|
||||
{
|
||||
return "MultiMC: " + name();
|
||||
}
|
||||
|
||||
QStringList BaseInstance::extraArguments() const
|
||||
{
|
||||
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
|
||||
}
|
@ -1,243 +0,0 @@
|
||||
/* Copyright 2013-2015 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 <QDateTime>
|
||||
#include <QSet>
|
||||
#include <QProcess>
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
|
||||
#include "settings/INIFile.h"
|
||||
#include "BaseVersionList.h"
|
||||
#include "minecraft/auth/MojangAccount.h"
|
||||
#include "launch/MessageLevel.h"
|
||||
#include "pathmatcher/IPathMatcher.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QDir;
|
||||
class Task;
|
||||
class LaunchTask;
|
||||
class BaseInstance;
|
||||
|
||||
// pointer for lazy people
|
||||
typedef std::shared_ptr<BaseInstance> InstancePtr;
|
||||
|
||||
/*!
|
||||
* \brief Base class for instances.
|
||||
* This class implements many functions that are common between instances and
|
||||
* provides a standard interface for all instances.
|
||||
*
|
||||
* To create a new instance type, create a new class inheriting from this class
|
||||
* and implement the pure virtual functions.
|
||||
*/
|
||||
class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance>
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
/// no-touchy!
|
||||
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
|
||||
|
||||
public:
|
||||
/// virtual destructor to make sure the destruction is COMPLETE
|
||||
virtual ~BaseInstance() {};
|
||||
|
||||
virtual void copy(const QDir &newDir) {}
|
||||
|
||||
virtual void init() = 0;
|
||||
|
||||
/// nuke thoroughly - deletes the instance contents, notifies the list/model which is
|
||||
/// responsible of cleaning up the husk
|
||||
void nuke();
|
||||
|
||||
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to
|
||||
/// be unique.
|
||||
virtual QString id() const;
|
||||
|
||||
void setRunning(bool running);
|
||||
bool isRunning() const;
|
||||
int64_t totalTimePlayed() const;
|
||||
void resetTimePlayed();
|
||||
|
||||
/// get the type of this instance
|
||||
QString instanceType() const;
|
||||
|
||||
/// Path to the instance's root directory.
|
||||
QString instanceRoot() const;
|
||||
|
||||
QString name() const;
|
||||
void setName(QString val);
|
||||
|
||||
/// Value used for instance window titles
|
||||
QString windowTitle() const;
|
||||
|
||||
QString iconKey() const;
|
||||
void setIconKey(QString val);
|
||||
|
||||
QString notes() const;
|
||||
void setNotes(QString val);
|
||||
|
||||
QString group() const;
|
||||
void setGroupInitial(QString val);
|
||||
void setGroupPost(QString val);
|
||||
|
||||
QString getPreLaunchCommand();
|
||||
QString getPostExitCommand();
|
||||
QString getWrapperCommand();
|
||||
|
||||
/// guess log level from a line of game log
|
||||
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
|
||||
{
|
||||
return level;
|
||||
};
|
||||
|
||||
virtual QStringList extraArguments() const;
|
||||
|
||||
virtual QString intendedVersionId() const = 0;
|
||||
virtual bool setIntendedVersionId(QString version) = 0;
|
||||
|
||||
/*!
|
||||
* The instance's current version.
|
||||
* This value represents the instance's current version. If this value is
|
||||
* different from the intendedVersion, the instance should be updated.
|
||||
* \warning Don't change this value unless you know what you're doing.
|
||||
*/
|
||||
virtual QString currentVersionId() const = 0;
|
||||
|
||||
/*!
|
||||
* Whether or not 'the game' should be downloaded when the instance is launched.
|
||||
*/
|
||||
virtual bool shouldUpdate() const = 0;
|
||||
virtual void setShouldUpdate(bool val) = 0;
|
||||
|
||||
/// Traits. Normally inside the version, depends on instance implementation.
|
||||
virtual QSet <QString> traits() = 0;
|
||||
|
||||
/**
|
||||
* Gets the time that the instance was last launched.
|
||||
* Stored in milliseconds since epoch.
|
||||
*/
|
||||
qint64 lastLaunch() const;
|
||||
/// Sets the last launched time to 'val' milliseconds since epoch
|
||||
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
|
||||
|
||||
InstancePtr getSharedPtr();
|
||||
|
||||
/*!
|
||||
* \brief Gets a pointer to this instance's version list.
|
||||
* \return A pointer to the available version list for this instance.
|
||||
*/
|
||||
virtual std::shared_ptr<BaseVersionList> versionList() const = 0;
|
||||
|
||||
/*!
|
||||
* \brief Gets this instance's settings object.
|
||||
* This settings object stores instance-specific settings.
|
||||
* \return A pointer to this instance's settings object.
|
||||
*/
|
||||
virtual SettingsObjectPtr settings() const;
|
||||
|
||||
/// returns a valid update task
|
||||
virtual std::shared_ptr<Task> createUpdateTask() = 0;
|
||||
|
||||
/// returns a valid launcher (task container)
|
||||
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
||||
|
||||
/*!
|
||||
* Returns a task that should be done right before launch
|
||||
* This task should do any extra preparations needed
|
||||
*/
|
||||
virtual std::shared_ptr<Task> createJarModdingTask() = 0;
|
||||
|
||||
/*!
|
||||
* Create envrironment variables for running the instance
|
||||
*/
|
||||
virtual QProcessEnvironment createEnvironment() = 0;
|
||||
|
||||
/*!
|
||||
* Returns a matcher that can maps relative paths within the instance to whether they are 'log files'
|
||||
*/
|
||||
virtual IPathMatcher::Ptr getLogFileMatcher() = 0;
|
||||
|
||||
/*!
|
||||
* Returns the root folder to use for looking up log files
|
||||
*/
|
||||
virtual QString getLogFileRoot() = 0;
|
||||
|
||||
/*!
|
||||
* does any necessary cleanups after the instance finishes. also runs before\
|
||||
* TODO: turn into a task that can run asynchronously
|
||||
*/
|
||||
virtual void cleanupAfterRun() = 0;
|
||||
|
||||
virtual QString getStatusbarDescription() = 0;
|
||||
|
||||
/// FIXME: this really should be elsewhere...
|
||||
virtual QString instanceConfigFolder() const = 0;
|
||||
|
||||
/// get variables this instance exports
|
||||
virtual QMap<QString, QString> getVariables() const = 0;
|
||||
|
||||
virtual QString typeName() const = 0;
|
||||
|
||||
enum InstanceFlag
|
||||
{
|
||||
VersionBrokenFlag = 0x01,
|
||||
UpdateAvailable = 0x02
|
||||
};
|
||||
Q_DECLARE_FLAGS(InstanceFlags, InstanceFlag)
|
||||
InstanceFlags flags() const;
|
||||
void setFlags(const InstanceFlags &flags);
|
||||
void setFlag(const InstanceFlag flag);
|
||||
void unsetFlag(const InstanceFlag flag);
|
||||
|
||||
bool canLaunch() const;
|
||||
virtual bool canExport() const = 0;
|
||||
|
||||
virtual bool reload();
|
||||
|
||||
signals:
|
||||
/*!
|
||||
* \brief Signal emitted when properties relevant to the instance view change
|
||||
*/
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
/*!
|
||||
* \brief Signal emitted when groups are affected in any way
|
||||
*/
|
||||
void groupChanged();
|
||||
/*!
|
||||
* \brief The instance just got nuked. Hurray!
|
||||
*/
|
||||
void nuked(BaseInstance *inst);
|
||||
|
||||
void flagsChanged();
|
||||
|
||||
protected slots:
|
||||
void iconUpdated(QString key);
|
||||
|
||||
protected:
|
||||
QString m_rootDir;
|
||||
QString m_group;
|
||||
SettingsObjectPtr m_settings;
|
||||
InstanceFlags m_flags;
|
||||
bool m_isRunning = false;
|
||||
QDateTime m_timeStarted;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)
|
||||
Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
|
@ -1,59 +0,0 @@
|
||||
/* Copyright 2013-2015 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 <memory>
|
||||
#include <QString>
|
||||
#include <QMetaType>
|
||||
|
||||
/*!
|
||||
* An abstract base class for versions.
|
||||
*/
|
||||
class BaseVersion
|
||||
{
|
||||
public:
|
||||
virtual ~BaseVersion() {}
|
||||
/*!
|
||||
* A string used to identify this version in config files.
|
||||
* This should be unique within the version list or shenanigans will occur.
|
||||
*/
|
||||
virtual QString descriptor() = 0;
|
||||
|
||||
/*!
|
||||
* The name of this version as it is displayed to the user.
|
||||
* For example: "1.5.1"
|
||||
*/
|
||||
virtual QString name() = 0;
|
||||
|
||||
/*!
|
||||
* This should return a string that describes
|
||||
* the kind of version this is (Stable, Beta, Snapshot, whatever)
|
||||
*/
|
||||
virtual QString typeString() const = 0;
|
||||
|
||||
virtual bool operator<(BaseVersion &a)
|
||||
{
|
||||
return name() < a.name();
|
||||
};
|
||||
virtual bool operator>(BaseVersion &a)
|
||||
{
|
||||
return name() > a.name();
|
||||
};
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
|
||||
|
||||
Q_DECLARE_METATYPE(BaseVersionPtr)
|
@ -1,104 +0,0 @@
|
||||
/* Copyright 2013-2015 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 "BaseVersionList.h"
|
||||
#include "BaseVersion.h"
|
||||
|
||||
BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor)
|
||||
{
|
||||
for (int i = 0; i < count(); i++)
|
||||
{
|
||||
if (at(i)->descriptor() == descriptor)
|
||||
return at(i);
|
||||
}
|
||||
return BaseVersionPtr();
|
||||
}
|
||||
|
||||
BaseVersionPtr BaseVersionList::getLatestStable() const
|
||||
{
|
||||
if (count() <= 0)
|
||||
return BaseVersionPtr();
|
||||
else
|
||||
return at(0);
|
||||
}
|
||||
|
||||
BaseVersionPtr BaseVersionList::getRecommended() const
|
||||
{
|
||||
return getLatestStable();
|
||||
}
|
||||
|
||||
QVariant BaseVersionList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
|
||||
BaseVersionPtr version = at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case VersionPointerRole:
|
||||
return qVariantFromValue(version);
|
||||
|
||||
case VersionRole:
|
||||
return version->name();
|
||||
|
||||
case VersionIdRole:
|
||||
return version->descriptor();
|
||||
|
||||
case TypeRole:
|
||||
return version->typeString();
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
BaseVersionList::RoleList BaseVersionList::providesRoles() const
|
||||
{
|
||||
return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole};
|
||||
}
|
||||
|
||||
int BaseVersionList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
}
|
||||
|
||||
int BaseVersionList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> BaseVersionList::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
|
||||
roles.insert(VersionRole, "version");
|
||||
roles.insert(VersionIdRole, "versionId");
|
||||
roles.insert(ParentGameVersionRole, "parentGameVersion");
|
||||
roles.insert(RecommendedRole, "recommended");
|
||||
roles.insert(LatestRole, "latest");
|
||||
roles.insert(TypeRole, "type");
|
||||
roles.insert(BranchRole, "branch");
|
||||
roles.insert(PathRole, "path");
|
||||
roles.insert(ArchitectureRole, "architecture");
|
||||
return roles;
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/* Copyright 2013-2015 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 <QAbstractListModel>
|
||||
|
||||
#include "BaseVersion.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
/*!
|
||||
* \brief Class that each instance type's version list derives from.
|
||||
* Version lists are the lists that keep track of the available game versions
|
||||
* for that instance. This list will not be loaded on startup. It will be loaded
|
||||
* when the list's load function is called. Before using the version list, you
|
||||
* should check to see if it has been loaded yet and if not, load the list.
|
||||
*
|
||||
* Note that this class also inherits from QAbstractListModel. Methods from that
|
||||
* class determine how this version list shows up in a list view. Said methods
|
||||
* all have a default implementation, but they can be overridden by plugins to
|
||||
* change the behavior of the list.
|
||||
*/
|
||||
class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ModelRoles
|
||||
{
|
||||
VersionPointerRole = Qt::UserRole,
|
||||
VersionRole,
|
||||
VersionIdRole,
|
||||
ParentGameVersionRole,
|
||||
RecommendedRole,
|
||||
LatestRole,
|
||||
TypeRole,
|
||||
BranchRole,
|
||||
PathRole,
|
||||
ArchitectureRole,
|
||||
SortRole
|
||||
};
|
||||
typedef QList<int> RoleList;
|
||||
|
||||
explicit BaseVersionList(QObject *parent = 0);
|
||||
|
||||
/*!
|
||||
* \brief Gets a task that will reload the version list.
|
||||
* Simply execute the task to load the list.
|
||||
* The task returned by this function should reset the model when it's done.
|
||||
* \return A pointer to a task that reloads the version list.
|
||||
*/
|
||||
virtual Task *getLoadTask() = 0;
|
||||
|
||||
//! Checks whether or not the list is loaded. If this returns false, the list should be
|
||||
//loaded.
|
||||
virtual bool isLoaded() = 0;
|
||||
|
||||
//! Gets the version at the given index.
|
||||
virtual const BaseVersionPtr at(int i) const = 0;
|
||||
|
||||
//! Returns the number of versions in the list.
|
||||
virtual int count() const = 0;
|
||||
|
||||
//////// List Model Functions ////////
|
||||
virtual QVariant data(const QModelIndex &index, int role) const;
|
||||
virtual int rowCount(const QModelIndex &parent) const;
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
virtual QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
//! which roles are provided by this version list?
|
||||
virtual RoleList providesRoles() const;
|
||||
|
||||
/*!
|
||||
* \brief Finds a version by its descriptor.
|
||||
* \param The descriptor of the version to find.
|
||||
* \return A const pointer to the version with the given descriptor. NULL if
|
||||
* one doesn't exist.
|
||||
*/
|
||||
virtual BaseVersionPtr findVersion(const QString &descriptor);
|
||||
|
||||
/*!
|
||||
* \brief Gets the latest stable version from this list
|
||||
*/
|
||||
virtual BaseVersionPtr getLatestStable() const;
|
||||
|
||||
/*!
|
||||
* \brief Gets the recommended version from this list
|
||||
* If the list doesn't support recommended versions, this works exactly as getLatestStable
|
||||
*/
|
||||
virtual BaseVersionPtr getRecommended() const;
|
||||
|
||||
/*!
|
||||
* Sorts the version list.
|
||||
*/
|
||||
virtual void sortVersions() = 0;
|
||||
|
||||
protected
|
||||
slots:
|
||||
/*!
|
||||
* Updates this list with the given list of versions.
|
||||
* This is done by copying each version in the given list and inserting it
|
||||
* into this one.
|
||||
* We need to do this so that we can set the parents of the versions are set to this
|
||||
* version list. This can't be done in the load task, because the versions the load
|
||||
* task creates are on the load task's thread and Qt won't allow their parents
|
||||
* to be set to something created on another thread.
|
||||
* To get around that problem, we invoke this method on the GUI thread, which
|
||||
* then copies the versions and sets their parents correctly.
|
||||
* \param versions List of versions whose parents should be set.
|
||||
*/
|
||||
virtual void updateListData(QList<BaseVersionPtr> versions) = 0;
|
||||
};
|
@ -1,344 +0,0 @@
|
||||
project(MultiMC_logic)
|
||||
|
||||
set(LOGIC_SOURCES
|
||||
# LOGIC - Base classes and infrastructure
|
||||
BaseInstaller.h
|
||||
BaseInstaller.cpp
|
||||
BaseVersionList.h
|
||||
BaseVersionList.cpp
|
||||
InstanceList.h
|
||||
InstanceList.cpp
|
||||
BaseVersion.h
|
||||
BaseInstance.h
|
||||
BaseInstance.cpp
|
||||
NullInstance.h
|
||||
MMCZip.h
|
||||
MMCZip.cpp
|
||||
MMCStrings.h
|
||||
MMCStrings.cpp
|
||||
BaseConfigObject.h
|
||||
BaseConfigObject.cpp
|
||||
AbstractCommonModel.h
|
||||
AbstractCommonModel.cpp
|
||||
TypeMagic.h
|
||||
|
||||
# Prefix tree where node names are strings between separators
|
||||
SeparatorPrefixTree.h
|
||||
|
||||
# WARNING: globals live here
|
||||
Env.h
|
||||
Env.cpp
|
||||
|
||||
# JSON parsing helpers
|
||||
Json.h
|
||||
Json.cpp
|
||||
|
||||
FileSystem.h
|
||||
FileSystem.cpp
|
||||
|
||||
Exception.h
|
||||
|
||||
# RW lock protected map
|
||||
RWStorage.h
|
||||
|
||||
# A variable that has an implicit default value and keeps track of changes
|
||||
DefaultVariable.h
|
||||
|
||||
# a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms
|
||||
QObjectPtr.h
|
||||
|
||||
# Resources
|
||||
resources/Resource.cpp
|
||||
resources/Resource.h
|
||||
resources/ResourceHandler.cpp
|
||||
resources/ResourceHandler.h
|
||||
resources/ResourceObserver.cpp
|
||||
resources/ResourceObserver.h
|
||||
resources/ResourceProxyModel.h
|
||||
resources/ResourceProxyModel.cpp
|
||||
|
||||
# Path matchers
|
||||
pathmatcher/FSTreeMatcher.h
|
||||
pathmatcher/IPathMatcher.h
|
||||
pathmatcher/MultiMatcher.h
|
||||
pathmatcher/RegexpMatcher.h
|
||||
|
||||
# Compression support
|
||||
GZip.h
|
||||
GZip.cpp
|
||||
|
||||
# Command line parameter parsing
|
||||
Commandline.h
|
||||
Commandline.cpp
|
||||
|
||||
# Version number string support
|
||||
Version.h
|
||||
Version.cpp
|
||||
|
||||
# network stuffs
|
||||
net/NetAction.h
|
||||
net/MD5EtagDownload.h
|
||||
net/MD5EtagDownload.cpp
|
||||
net/ByteArrayDownload.h
|
||||
net/ByteArrayDownload.cpp
|
||||
net/CacheDownload.h
|
||||
net/CacheDownload.cpp
|
||||
net/NetJob.h
|
||||
net/NetJob.cpp
|
||||
net/HttpMetaCache.h
|
||||
net/HttpMetaCache.cpp
|
||||
net/PasteUpload.h
|
||||
net/PasteUpload.cpp
|
||||
net/URLConstants.h
|
||||
net/URLConstants.cpp
|
||||
|
||||
# Yggdrasil login stuff
|
||||
minecraft/auth/AuthSession.h
|
||||
minecraft/auth/AuthSession.cpp
|
||||
minecraft/auth/MojangAccountList.h
|
||||
minecraft/auth/MojangAccountList.cpp
|
||||
minecraft/auth/MojangAccount.h
|
||||
minecraft/auth/MojangAccount.cpp
|
||||
minecraft/auth/YggdrasilTask.h
|
||||
minecraft/auth/YggdrasilTask.cpp
|
||||
minecraft/auth/flows/AuthenticateTask.h
|
||||
minecraft/auth/flows/AuthenticateTask.cpp
|
||||
minecraft/auth/flows/RefreshTask.cpp
|
||||
minecraft/auth/flows/RefreshTask.cpp
|
||||
minecraft/auth/flows/ValidateTask.h
|
||||
minecraft/auth/flows/ValidateTask.cpp
|
||||
|
||||
# Game launch logic
|
||||
launch/steps/CheckJava.cpp
|
||||
launch/steps/CheckJava.h
|
||||
launch/steps/LaunchMinecraft.cpp
|
||||
launch/steps/LaunchMinecraft.h
|
||||
launch/steps/ModMinecraftJar.cpp
|
||||
launch/steps/ModMinecraftJar.h
|
||||
launch/steps/PostLaunchCommand.cpp
|
||||
launch/steps/PostLaunchCommand.h
|
||||
launch/steps/PreLaunchCommand.cpp
|
||||
launch/steps/PreLaunchCommand.h
|
||||
launch/steps/TextPrint.cpp
|
||||
launch/steps/TextPrint.h
|
||||
launch/steps/Update.cpp
|
||||
launch/steps/Update.h
|
||||
launch/LaunchStep.cpp
|
||||
launch/LaunchStep.h
|
||||
launch/LaunchTask.cpp
|
||||
launch/LaunchTask.h
|
||||
launch/LoggedProcess.cpp
|
||||
launch/LoggedProcess.h
|
||||
launch/MessageLevel.cpp
|
||||
launch/MessageLevel.h
|
||||
|
||||
# Update system
|
||||
updater/GoUpdate.h
|
||||
updater/GoUpdate.cpp
|
||||
updater/UpdateChecker.h
|
||||
updater/UpdateChecker.cpp
|
||||
updater/DownloadTask.h
|
||||
updater/DownloadTask.cpp
|
||||
|
||||
# Notifications - short warning messages
|
||||
notifications/NotificationChecker.h
|
||||
notifications/NotificationChecker.cpp
|
||||
|
||||
# News System
|
||||
news/NewsChecker.h
|
||||
news/NewsChecker.cpp
|
||||
news/NewsEntry.h
|
||||
news/NewsEntry.cpp
|
||||
|
||||
# Status system
|
||||
status/StatusChecker.h
|
||||
status/StatusChecker.cpp
|
||||
|
||||
# Minecraft support
|
||||
minecraft/onesix/OneSixUpdate.h
|
||||
minecraft/onesix/OneSixUpdate.cpp
|
||||
minecraft/onesix/OneSixInstance.h
|
||||
minecraft/onesix/OneSixInstance.cpp
|
||||
minecraft/onesix/OneSixProfileStrategy.cpp
|
||||
minecraft/onesix/OneSixProfileStrategy.h
|
||||
minecraft/onesix/OneSixVersionFormat.cpp
|
||||
minecraft/onesix/OneSixVersionFormat.h
|
||||
minecraft/legacy/LegacyUpdate.h
|
||||
minecraft/legacy/LegacyUpdate.cpp
|
||||
minecraft/legacy/LegacyInstance.h
|
||||
minecraft/legacy/LegacyInstance.cpp
|
||||
minecraft/legacy/LwjglVersionList.h
|
||||
minecraft/legacy/LwjglVersionList.cpp
|
||||
minecraft/GradleSpecifier.h
|
||||
minecraft/MinecraftProfile.cpp
|
||||
minecraft/MinecraftProfile.h
|
||||
minecraft/MojangVersionFormat.cpp
|
||||
minecraft/MojangVersionFormat.h
|
||||
minecraft/JarMod.h
|
||||
minecraft/MinecraftInstance.cpp
|
||||
minecraft/MinecraftInstance.h
|
||||
minecraft/MinecraftVersion.cpp
|
||||
minecraft/MinecraftVersion.h
|
||||
minecraft/MinecraftVersionList.cpp
|
||||
minecraft/MinecraftVersionList.h
|
||||
minecraft/Rule.cpp
|
||||
minecraft/Rule.h
|
||||
minecraft/OpSys.cpp
|
||||
minecraft/OpSys.h
|
||||
minecraft/ParseUtils.cpp
|
||||
minecraft/ParseUtils.h
|
||||
minecraft/ProfileUtils.cpp
|
||||
minecraft/ProfileUtils.h
|
||||
minecraft/ProfileStrategy.h
|
||||
minecraft/Library.cpp
|
||||
minecraft/Library.h
|
||||
minecraft/MojangDownloadInfo.h
|
||||
minecraft/VersionBuildError.h
|
||||
minecraft/VersionFile.cpp
|
||||
minecraft/VersionFile.h
|
||||
minecraft/ProfilePatch.h
|
||||
minecraft/VersionFilterData.h
|
||||
minecraft/VersionFilterData.cpp
|
||||
minecraft/Mod.h
|
||||
minecraft/Mod.cpp
|
||||
minecraft/ModList.h
|
||||
minecraft/ModList.cpp
|
||||
minecraft/World.h
|
||||
minecraft/World.cpp
|
||||
minecraft/WorldList.h
|
||||
minecraft/WorldList.cpp
|
||||
|
||||
# FTB
|
||||
minecraft/ftb/OneSixFTBInstance.h
|
||||
minecraft/ftb/OneSixFTBInstance.cpp
|
||||
minecraft/ftb/LegacyFTBInstance.h
|
||||
minecraft/ftb/LegacyFTBInstance.cpp
|
||||
minecraft/ftb/FTBProfileStrategy.h
|
||||
minecraft/ftb/FTBProfileStrategy.cpp
|
||||
minecraft/ftb/FTBPlugin.h
|
||||
minecraft/ftb/FTBPlugin.cpp
|
||||
|
||||
# A Recursive file system watcher
|
||||
RecursiveFileSystemWatcher.h
|
||||
RecursiveFileSystemWatcher.cpp
|
||||
|
||||
# the screenshots feature
|
||||
screenshots/Screenshot.h
|
||||
screenshots/ImgurUpload.h
|
||||
screenshots/ImgurUpload.cpp
|
||||
screenshots/ImgurAlbumCreation.h
|
||||
screenshots/ImgurAlbumCreation.cpp
|
||||
|
||||
# Tasks
|
||||
tasks/Task.h
|
||||
tasks/Task.cpp
|
||||
tasks/ThreadTask.h
|
||||
tasks/ThreadTask.cpp
|
||||
tasks/SequentialTask.h
|
||||
tasks/SequentialTask.cpp
|
||||
|
||||
# Settings
|
||||
settings/INIFile.cpp
|
||||
settings/INIFile.h
|
||||
settings/INISettingsObject.cpp
|
||||
settings/INISettingsObject.h
|
||||
settings/OverrideSetting.cpp
|
||||
settings/OverrideSetting.h
|
||||
settings/PassthroughSetting.cpp
|
||||
settings/PassthroughSetting.h
|
||||
settings/Setting.cpp
|
||||
settings/Setting.h
|
||||
settings/SettingsObject.cpp
|
||||
settings/SettingsObject.h
|
||||
|
||||
# Java related code
|
||||
java/JavaChecker.h
|
||||
java/JavaChecker.cpp
|
||||
java/JavaCheckerJob.h
|
||||
java/JavaCheckerJob.cpp
|
||||
java/JavaInstall.h
|
||||
java/JavaInstall.cpp
|
||||
java/JavaInstallList.h
|
||||
java/JavaInstallList.cpp
|
||||
java/JavaUtils.h
|
||||
java/JavaUtils.cpp
|
||||
java/JavaVersion.h
|
||||
java/JavaVersion.cpp
|
||||
|
||||
# Assets
|
||||
minecraft/AssetsUtils.h
|
||||
minecraft/AssetsUtils.cpp
|
||||
|
||||
# Forge and all things forge related
|
||||
minecraft/forge/ForgeVersion.h
|
||||
minecraft/forge/ForgeVersion.cpp
|
||||
minecraft/forge/ForgeVersionList.h
|
||||
minecraft/forge/ForgeVersionList.cpp
|
||||
minecraft/forge/ForgeXzDownload.h
|
||||
minecraft/forge/ForgeXzDownload.cpp
|
||||
minecraft/forge/LegacyForge.h
|
||||
minecraft/forge/LegacyForge.cpp
|
||||
minecraft/forge/ForgeInstaller.h
|
||||
minecraft/forge/ForgeInstaller.cpp
|
||||
|
||||
# Liteloader and related things
|
||||
minecraft/liteloader/LiteLoaderInstaller.h
|
||||
minecraft/liteloader/LiteLoaderInstaller.cpp
|
||||
minecraft/liteloader/LiteLoaderVersionList.h
|
||||
minecraft/liteloader/LiteLoaderVersionList.cpp
|
||||
|
||||
# Translations
|
||||
trans/TranslationDownloader.h
|
||||
trans/TranslationDownloader.cpp
|
||||
|
||||
# Tools
|
||||
tools/BaseExternalTool.cpp
|
||||
tools/BaseExternalTool.h
|
||||
tools/BaseProfiler.cpp
|
||||
tools/BaseProfiler.h
|
||||
tools/JProfiler.cpp
|
||||
tools/JProfiler.h
|
||||
tools/JVisualVM.cpp
|
||||
tools/JVisualVM.h
|
||||
tools/MCEditTool.cpp
|
||||
tools/MCEditTool.h
|
||||
|
||||
# Wonko
|
||||
wonko/tasks/BaseWonkoEntityRemoteLoadTask.cpp
|
||||
wonko/tasks/BaseWonkoEntityRemoteLoadTask.h
|
||||
wonko/tasks/BaseWonkoEntityLocalLoadTask.cpp
|
||||
wonko/tasks/BaseWonkoEntityLocalLoadTask.h
|
||||
wonko/format/WonkoFormatV1.cpp
|
||||
wonko/format/WonkoFormatV1.h
|
||||
wonko/format/WonkoFormat.cpp
|
||||
wonko/format/WonkoFormat.h
|
||||
wonko/BaseWonkoEntity.cpp
|
||||
wonko/BaseWonkoEntity.h
|
||||
wonko/WonkoVersionList.cpp
|
||||
wonko/WonkoVersionList.h
|
||||
wonko/WonkoVersion.cpp
|
||||
wonko/WonkoVersion.h
|
||||
wonko/WonkoIndex.cpp
|
||||
wonko/WonkoIndex.h
|
||||
wonko/WonkoUtil.cpp
|
||||
wonko/WonkoUtil.h
|
||||
wonko/WonkoReference.cpp
|
||||
wonko/WonkoReference.h
|
||||
)
|
||||
################################ COMPILE ################################
|
||||
|
||||
# we need zlib
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
|
||||
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
|
||||
|
||||
generate_export_header(MultiMC_logic)
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC_logic xz-embedded unpack200 ${QUAZIP_LIBRARIES} nbt++ ${ZLIB_LIBRARIES})
|
||||
qt5_use_modules(MultiMC_logic Core Xml Network Concurrent)
|
||||
add_dependencies(MultiMC_logic QuaZIP)
|
||||
|
||||
# Mark and export headers
|
||||
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
|
@ -1,483 +0,0 @@
|
||||
/* Copyright 2013-2015 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 "Commandline.h"
|
||||
|
||||
/**
|
||||
* @file libutil/src/cmdutils.cpp
|
||||
*/
|
||||
|
||||
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())
|
||||
{
|
||||
}
|
||||
}
|
@ -1,252 +0,0 @@
|
||||
/* Copyright 2013-2015 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 "multimc_logic_export.h"
|
||||
|
||||
/**
|
||||
* @file libutil/include/cmdutils.h
|
||||
* @brief commandline parsing and processing utilities
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
||||
MULTIMC_LOGIC_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 MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
ParsingError(const QString &what);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The Parser class
|
||||
*/
|
||||
class MULTIMC_LOGIC_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);
|
||||
};
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
template <typename T>
|
||||
class DefaultVariable
|
||||
{
|
||||
public:
|
||||
DefaultVariable(const T & value)
|
||||
{
|
||||
defaultValue = value;
|
||||
}
|
||||
DefaultVariable<T> & operator =(const T & value)
|
||||
{
|
||||
currentValue = value;
|
||||
is_default = currentValue == defaultValue;
|
||||
is_explicit = true;
|
||||
return *this;
|
||||
}
|
||||
operator const T &() const
|
||||
{
|
||||
return is_default ? defaultValue : currentValue;
|
||||
}
|
||||
bool isDefault() const
|
||||
{
|
||||
return is_default;
|
||||
}
|
||||
bool isExplicit() const
|
||||
{
|
||||
return is_explicit;
|
||||
}
|
||||
private:
|
||||
T currentValue;
|
||||
T defaultValue;
|
||||
bool is_default = true;
|
||||
bool is_explicit = false;
|
||||
};
|
@ -1,222 +0,0 @@
|
||||
#include "Env.h"
|
||||
#include "net/HttpMetaCache.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "BaseVersionList.h"
|
||||
#include <QDir>
|
||||
#include <QNetworkProxy>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QDebug>
|
||||
#include "tasks/Task.h"
|
||||
#include "wonko/WonkoIndex.h"
|
||||
#include <QDebug>
|
||||
|
||||
/*
|
||||
* The *NEW* global rat nest of an object. Handle with care.
|
||||
*/
|
||||
|
||||
Env::Env()
|
||||
{
|
||||
m_qnam = std::make_shared<QNetworkAccessManager>();
|
||||
}
|
||||
|
||||
void Env::destroy()
|
||||
{
|
||||
m_metacache.reset();
|
||||
m_qnam.reset();
|
||||
m_versionLists.clear();
|
||||
}
|
||||
|
||||
Env& Env::Env::getInstance()
|
||||
{
|
||||
static Env instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::shared_ptr< HttpMetaCache > Env::metacache()
|
||||
{
|
||||
Q_ASSERT(m_metacache != nullptr);
|
||||
return m_metacache;
|
||||
}
|
||||
|
||||
std::shared_ptr< QNetworkAccessManager > Env::qnam()
|
||||
{
|
||||
return m_qnam;
|
||||
}
|
||||
|
||||
/*
|
||||
class NullVersion : public BaseVersion
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual QString name()
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
virtual QString descriptor()
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
virtual QString typeString() const
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
};
|
||||
|
||||
class NullTask: public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual void executeTask()
|
||||
{
|
||||
emitFailed(tr("Nothing to do."));
|
||||
}
|
||||
};
|
||||
|
||||
class NullVersionList: public BaseVersionList
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual const BaseVersionPtr at(int i) const
|
||||
{
|
||||
return std::make_shared<NullVersion>();
|
||||
}
|
||||
virtual int count() const
|
||||
{
|
||||
return 0;
|
||||
};
|
||||
virtual Task* getLoadTask()
|
||||
{
|
||||
return new NullTask;
|
||||
}
|
||||
virtual bool isLoaded()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual void sort()
|
||||
{
|
||||
}
|
||||
virtual void updateListData(QList< BaseVersionPtr >)
|
||||
{
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
BaseVersionPtr Env::getVersion(QString component, QString version)
|
||||
{
|
||||
auto list = getVersionList(component);
|
||||
if(!list)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return list->findVersion(version);
|
||||
}
|
||||
|
||||
std::shared_ptr< BaseVersionList > Env::getVersionList(QString component)
|
||||
{
|
||||
auto iter = m_versionLists.find(component);
|
||||
if(iter != m_versionLists.end())
|
||||
{
|
||||
return *iter;
|
||||
}
|
||||
//return std::make_shared<NullVersionList>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Env::registerVersionList(QString name, std::shared_ptr< BaseVersionList > vlist)
|
||||
{
|
||||
m_versionLists[name] = vlist;
|
||||
}
|
||||
|
||||
std::shared_ptr<WonkoIndex> Env::wonkoIndex()
|
||||
{
|
||||
if (!m_wonkoIndex)
|
||||
{
|
||||
m_wonkoIndex = std::make_shared<WonkoIndex>();
|
||||
}
|
||||
return m_wonkoIndex;
|
||||
}
|
||||
|
||||
|
||||
void Env::initHttpMetaCache()
|
||||
{
|
||||
m_metacache.reset(new HttpMetaCache("metacache"));
|
||||
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
|
||||
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
|
||||
m_metacache->addBase("versions", QDir("versions").absolutePath());
|
||||
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
|
||||
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
|
||||
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
|
||||
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
|
||||
m_metacache->addBase("general", QDir("cache").absolutePath());
|
||||
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
|
||||
m_metacache->addBase("root", QDir::currentPath());
|
||||
m_metacache->addBase("translations", QDir("translations").absolutePath());
|
||||
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
|
||||
m_metacache->addBase("wonko", QDir("cache/wonko").absolutePath());
|
||||
m_metacache->Load();
|
||||
}
|
||||
|
||||
void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password)
|
||||
{
|
||||
// Set the application proxy settings.
|
||||
if (proxyTypeStr == "SOCKS5")
|
||||
{
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password));
|
||||
}
|
||||
else if (proxyTypeStr == "HTTP")
|
||||
{
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password));
|
||||
}
|
||||
else if (proxyTypeStr == "None")
|
||||
{
|
||||
// If we have no proxy set, set no proxy and return.
|
||||
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have "Default" selected, set Qt to use the system proxy settings.
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
}
|
||||
|
||||
qDebug() << "Detecting proxy settings...";
|
||||
QNetworkProxy proxy = QNetworkProxy::applicationProxy();
|
||||
if (m_qnam.get())
|
||||
m_qnam->setProxy(proxy);
|
||||
QString proxyDesc;
|
||||
if (proxy.type() == QNetworkProxy::NoProxy)
|
||||
{
|
||||
qDebug() << "Using no proxy is an option!";
|
||||
return;
|
||||
}
|
||||
switch (proxy.type())
|
||||
{
|
||||
case QNetworkProxy::DefaultProxy:
|
||||
proxyDesc = "Default proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::Socks5Proxy:
|
||||
proxyDesc = "Socks5 proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpProxy:
|
||||
proxyDesc = "HTTP proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpCachingProxy:
|
||||
proxyDesc = "HTTP caching: ";
|
||||
break;
|
||||
case QNetworkProxy::FtpCachingProxy:
|
||||
proxyDesc = "FTP caching: ";
|
||||
break;
|
||||
default:
|
||||
proxyDesc = "DERP proxy: ";
|
||||
break;
|
||||
}
|
||||
proxyDesc += QString("%3@%1:%2 pass %4")
|
||||
.arg(proxy.hostName())
|
||||
.arg(proxy.port())
|
||||
.arg(proxy.user())
|
||||
.arg(proxy.password());
|
||||
qDebug() << proxyDesc;
|
||||
}
|
||||
|
||||
#include "Env.moc"
|
@ -1,60 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class HttpMetaCache;
|
||||
class BaseVersionList;
|
||||
class BaseVersion;
|
||||
class WonkoIndex;
|
||||
|
||||
#if defined(ENV)
|
||||
#undef ENV
|
||||
#endif
|
||||
#define ENV (Env::getInstance())
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Env
|
||||
{
|
||||
friend class MultiMC;
|
||||
private:
|
||||
Env();
|
||||
public:
|
||||
static Env& getInstance();
|
||||
|
||||
// call when Qt stuff is being torn down
|
||||
void destroy();
|
||||
|
||||
std::shared_ptr<QNetworkAccessManager> qnam();
|
||||
|
||||
std::shared_ptr<HttpMetaCache> metacache();
|
||||
|
||||
/// init the cache. FIXME: possible future hook point
|
||||
void initHttpMetaCache();
|
||||
|
||||
/// Updates the application proxy settings from the settings object.
|
||||
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
|
||||
|
||||
/// get a version list by name
|
||||
std::shared_ptr<BaseVersionList> getVersionList(QString component);
|
||||
|
||||
/// get a version by list name and version name
|
||||
std::shared_ptr<BaseVersion> getVersion(QString component, QString version);
|
||||
|
||||
void registerVersionList(QString name, std::shared_ptr<BaseVersionList> vlist);
|
||||
|
||||
std::shared_ptr<WonkoIndex> wonkoIndex();
|
||||
|
||||
QString wonkoRootUrl() const { return m_wonkoRootUrl; }
|
||||
void setWonkoRootUrl(const QString &url) { m_wonkoRootUrl = url; }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<QNetworkAccessManager> m_qnam;
|
||||
std::shared_ptr<HttpMetaCache> m_metacache;
|
||||
QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists;
|
||||
std::shared_ptr<WonkoIndex> m_wonkoIndex;
|
||||
QString m_wonkoRootUrl;
|
||||
};
|
@ -1,34 +0,0 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
#include <exception>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Exception : public std::exception
|
||||
{
|
||||
public:
|
||||
Exception(const QString &message) : std::exception(), m_message(message)
|
||||
{
|
||||
qCritical() << "Exception:" << message;
|
||||
}
|
||||
Exception(const Exception &other)
|
||||
: std::exception(), m_message(other.cause())
|
||||
{
|
||||
}
|
||||
virtual ~Exception() noexcept {}
|
||||
const char *what() const noexcept
|
||||
{
|
||||
return m_message.toLatin1().constData();
|
||||
}
|
||||
QString cause() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_message;
|
||||
};
|
@ -1,436 +0,0 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QSaveFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace FS {
|
||||
|
||||
void ensureExists(const QDir &dir)
|
||||
{
|
||||
if (!QDir().mkpath(dir.absolutePath()))
|
||||
{
|
||||
throw FileSystemException("Unable to create directory " + dir.dirName() + " (" +
|
||||
dir.absolutePath() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
void write(const QString &filename, const QByteArray &data)
|
||||
{
|
||||
ensureExists(QFileInfo(filename).dir());
|
||||
QSaveFile file(filename);
|
||||
if (!file.open(QSaveFile::WriteOnly))
|
||||
{
|
||||
throw FileSystemException("Couldn't open " + filename + " for writing: " +
|
||||
file.errorString());
|
||||
}
|
||||
if (data.size() != file.write(data))
|
||||
{
|
||||
throw FileSystemException("Error writing data to " + filename + ": " +
|
||||
file.errorString());
|
||||
}
|
||||
if (!file.commit())
|
||||
{
|
||||
throw FileSystemException("Error while committing data to " + filename + ": " +
|
||||
file.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray read(const QString &filename)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
{
|
||||
throw FileSystemException("Unable to open " + filename + " for reading: " +
|
||||
file.errorString());
|
||||
}
|
||||
const qint64 size = file.size();
|
||||
QByteArray data(int(size), 0);
|
||||
const qint64 ret = file.read(data.data(), size);
|
||||
if (ret == -1 || ret != size)
|
||||
{
|
||||
throw FileSystemException("Error reading data from " + filename + ": " +
|
||||
file.errorString());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
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 copy::operator()(const QString &offset)
|
||||
{
|
||||
//NOTE always deep copy on windows. the alternatives are too messy.
|
||||
#if defined Q_OS_WIN32
|
||||
m_followSymlinks = true;
|
||||
#endif
|
||||
|
||||
auto src = PathCombine(m_src.absolutePath(), offset);
|
||||
auto dst = PathCombine(m_dst.absolutePath(), offset);
|
||||
|
||||
QFileInfo currentSrc(src);
|
||||
if (!currentSrc.exists())
|
||||
return false;
|
||||
|
||||
if(!m_followSymlinks && currentSrc.isSymLink())
|
||||
{
|
||||
qDebug() << "creating symlink" << src << " - " << dst;
|
||||
if (!ensureFilePathExists(dst))
|
||||
{
|
||||
qWarning() << "Cannot create path!";
|
||||
return false;
|
||||
}
|
||||
return QFile::link(currentSrc.symLinkTarget(), dst);
|
||||
}
|
||||
else if(currentSrc.isFile())
|
||||
{
|
||||
qDebug() << "copying file" << src << " - " << dst;
|
||||
if (!ensureFilePathExists(dst))
|
||||
{
|
||||
qWarning() << "Cannot create path!";
|
||||
return false;
|
||||
}
|
||||
return QFile::copy(src, dst);
|
||||
}
|
||||
else if(currentSrc.isDir())
|
||||
{
|
||||
qDebug() << "recursing" << offset;
|
||||
if (!ensureFolderPathExists(dst))
|
||||
{
|
||||
qWarning() << "Cannot create path!";
|
||||
return false;
|
||||
}
|
||||
QDir currentDir(src);
|
||||
for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
|
||||
{
|
||||
auto inner_offset = PathCombine(offset, f);
|
||||
// ignore and skip stuff that matches the blacklist.
|
||||
if(m_blacklist && m_blacklist->matches(inner_offset))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(!operator()(inner_offset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#endif
|
||||
bool deletePath(QString path)
|
||||
{
|
||||
bool OK = true;
|
||||
QDir dir(path);
|
||||
|
||||
if (!dir.exists())
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
|
||||
QDir::AllDirs | QDir::Files,
|
||||
QDir::DirsFirst);
|
||||
|
||||
for(auto & info: allEntries)
|
||||
{
|
||||
#if defined Q_OS_WIN32
|
||||
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
|
||||
auto wString = nativePath.toStdWString();
|
||||
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
|
||||
// Windows: check for junctions, reparse points and other nasty things of that sort
|
||||
if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
{
|
||||
if (info.isFile())
|
||||
{
|
||||
OK &= QFile::remove(info.absoluteFilePath());
|
||||
}
|
||||
else if (info.isDir())
|
||||
{
|
||||
OK &= dir.rmdir(info.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
#else
|
||||
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
|
||||
if(info.isSymLink())
|
||||
{
|
||||
OK &= QFile::remove(info.absoluteFilePath());
|
||||
}
|
||||
#endif
|
||||
else if (info.isDir())
|
||||
{
|
||||
OK &= deletePath(info.absoluteFilePath());
|
||||
}
|
||||
else if (info.isFile())
|
||||
{
|
||||
OK &= QFile::remove(info.absoluteFilePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
OK = false;
|
||||
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
OK &= dir.rmdir(dir.absolutePath());
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
QString PathCombine(QString path1, QString path2)
|
||||
{
|
||||
if(!path1.size())
|
||||
return path2;
|
||||
if(!path2.size())
|
||||
return path1;
|
||||
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();
|
||||
}
|
||||
|
||||
QString ResolveExecutable(QString path)
|
||||
{
|
||||
if (path.isEmpty())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
if(!path.contains('/'))
|
||||
{
|
||||
path = QStandardPaths::findExecutable(path);
|
||||
}
|
||||
QFileInfo pathInfo(path);
|
||||
if(!pathInfo.exists() || !pathInfo.isExecutable())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
return pathInfo.absoluteFilePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 baseName = RemoveInvalidFilenameChars(string, '-');
|
||||
QString dirName;
|
||||
do
|
||||
{
|
||||
if(num == 0)
|
||||
{
|
||||
dirName = baseName;
|
||||
}
|
||||
else
|
||||
{
|
||||
dirName = baseName + QString::number(num);;
|
||||
}
|
||||
|
||||
// If it's over 9000
|
||||
if (num > 9000)
|
||||
return "";
|
||||
num++;
|
||||
} while (QFileInfo(PathCombine(inDir, dirName)).exists());
|
||||
return dirName;
|
||||
}
|
||||
|
||||
// Does the directory path contain any '!'? If yes, return true, otherwise false.
|
||||
// (This is a problem for Java)
|
||||
bool checkProblemticPathJava(QDir folder)
|
||||
{
|
||||
QString pathfoldername = folder.absolutePath();
|
||||
return pathfoldername.contains("!", Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
// Win32 crap
|
||||
#if defined Q_OS_WIN
|
||||
|
||||
#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 getDesktopDir()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
}
|
||||
|
||||
// Cross-platform Shortcut creation
|
||||
bool createShortCut(QString location, QString dest, QStringList args, QString name,
|
||||
QString icon)
|
||||
{
|
||||
#if defined Q_OS_LINUX
|
||||
location = PathCombine(location, name + ".desktop");
|
||||
|
||||
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 defined Q_OS_WIN
|
||||
// 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
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Exception.h"
|
||||
#include "pathmatcher/IPathMatcher.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
#include <QDir>
|
||||
#include <QFlags>
|
||||
|
||||
namespace FS
|
||||
{
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception
|
||||
{
|
||||
public:
|
||||
FileSystemException(const QString &message) : Exception(message) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* write data to a file safely
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT void write(const QString &filename, const QByteArray &data);
|
||||
|
||||
/**
|
||||
* read data from a file safely\
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT QByteArray read(const QString &filename);
|
||||
|
||||
/**
|
||||
* 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!
|
||||
*/
|
||||
MULTIMC_LOGIC_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!
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath);
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT copy
|
||||
{
|
||||
public:
|
||||
copy(const copy&) = delete;
|
||||
copy(const QString & src, const QString & dst)
|
||||
{
|
||||
m_src = src;
|
||||
m_dst = dst;
|
||||
}
|
||||
copy & followSymlinks(const bool follow)
|
||||
{
|
||||
m_followSymlinks = follow;
|
||||
return *this;
|
||||
}
|
||||
copy & blacklist(const IPathMatcher * filter)
|
||||
{
|
||||
m_blacklist = filter;
|
||||
return *this;
|
||||
}
|
||||
bool operator()()
|
||||
{
|
||||
return operator()(QString());
|
||||
}
|
||||
|
||||
private:
|
||||
bool operator()(const QString &offset);
|
||||
|
||||
private:
|
||||
bool m_followSymlinks = true;
|
||||
const IPathMatcher * m_blacklist = nullptr;
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a folder recursively
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT bool deletePath(QString path);
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2);
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2, QString path3);
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);
|
||||
|
||||
/**
|
||||
* Resolve an executable
|
||||
*
|
||||
* Will resolve:
|
||||
* single executable (by name)
|
||||
* relative path
|
||||
* absolute path
|
||||
*
|
||||
* @return absolute path to executable or null string
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT QString ResolveExecutable(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)
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT QString NormalizePath(QString path);
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-');
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString DirNameFromString(QString string, QString inDir = ".");
|
||||
|
||||
/// Checks if the a given Path contains "!"
|
||||
MULTIMC_LOGIC_EXPORT bool checkProblemticPathJava(QDir folder);
|
||||
|
||||
// Get the Directory representing the User's Desktop
|
||||
MULTIMC_LOGIC_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
|
||||
MULTIMC_LOGIC_EXPORT bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
#include "GZip.h"
|
||||
#include <zlib.h>
|
||||
#include <QByteArray>
|
||||
|
||||
bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes)
|
||||
{
|
||||
if (compressedBytes.size() == 0)
|
||||
{
|
||||
uncompressedBytes = compressedBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned uncompLength = compressedBytes.size();
|
||||
uncompressedBytes.clear();
|
||||
uncompressedBytes.resize(uncompLength);
|
||||
|
||||
z_stream strm;
|
||||
memset(&strm, 0, sizeof(strm));
|
||||
strm.next_in = (Bytef *)compressedBytes.data();
|
||||
strm.avail_in = compressedBytes.size();
|
||||
|
||||
bool done = false;
|
||||
|
||||
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = Z_OK;
|
||||
|
||||
while (!done)
|
||||
{
|
||||
// If our output buffer is too small
|
||||
if (strm.total_out >= uncompLength)
|
||||
{
|
||||
uncompressedBytes.resize(uncompLength * 2);
|
||||
uncompLength *= 2;
|
||||
}
|
||||
|
||||
strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out);
|
||||
strm.avail_out = uncompLength - strm.total_out;
|
||||
|
||||
// Inflate another chunk.
|
||||
err = inflate(&strm, Z_SYNC_FLUSH);
|
||||
if (err == Z_STREAM_END)
|
||||
done = true;
|
||||
else if (err != Z_OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (inflateEnd(&strm) != Z_OK || !done)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uncompressedBytes.resize(strm.total_out);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
|
||||
{
|
||||
if (uncompressedBytes.size() == 0)
|
||||
{
|
||||
compressedBytes = uncompressedBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned compLength = std::min(uncompressedBytes.size(), 16);
|
||||
compressedBytes.clear();
|
||||
compressedBytes.resize(compLength);
|
||||
|
||||
z_stream zs;
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
zs.next_in = (Bytef*)uncompressedBytes.data();
|
||||
zs.avail_in = uncompressedBytes.size();
|
||||
|
||||
int ret;
|
||||
compressedBytes.resize(uncompressedBytes.size());
|
||||
|
||||
unsigned offset = 0;
|
||||
unsigned temp = 0;
|
||||
do
|
||||
{
|
||||
auto remaining = compressedBytes.size() - offset;
|
||||
if(remaining < 1)
|
||||
{
|
||||
compressedBytes.resize(compressedBytes.size() * 2);
|
||||
}
|
||||
zs.next_out = (Bytef *) (compressedBytes.data() + offset);
|
||||
temp = zs.avail_out = compressedBytes.size() - offset;
|
||||
ret = deflate(&zs, Z_FINISH);
|
||||
offset += temp - zs.avail_out;
|
||||
} while (ret == Z_OK);
|
||||
|
||||
compressedBytes.resize(offset);
|
||||
|
||||
if (deflateEnd(&zs) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ret != Z_STREAM_END)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
#include <QByteArray>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT GZip
|
||||
{
|
||||
public:
|
||||
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
|
||||
static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes);
|
||||
};
|
||||
|
@ -1,580 +0,0 @@
|
||||
/* Copyright 2013-2015 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 <QDir>
|
||||
#include <QSet>
|
||||
#include <QFile>
|
||||
#include <QDirIterator>
|
||||
#include <QThread>
|
||||
#include <QTextStream>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QRegularExpression>
|
||||
#include <QDebug>
|
||||
|
||||
#include "InstanceList.h"
|
||||
#include "BaseInstance.h"
|
||||
|
||||
//FIXME: this really doesn't belong *here*
|
||||
#include "minecraft/onesix/OneSixInstance.h"
|
||||
#include "minecraft/legacy/LegacyInstance.h"
|
||||
#include "minecraft/ftb/FTBPlugin.h"
|
||||
#include "minecraft/MinecraftVersion.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "NullInstance.h"
|
||||
#include "FileSystem.h"
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
|
||||
const static int GROUP_FILE_FORMAT_VERSION = 1;
|
||||
|
||||
InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
|
||||
: QAbstractListModel(parent), m_instDir(instDir)
|
||||
{
|
||||
m_globalSettings = globalSettings;
|
||||
if (!QDir::current().exists(m_instDir))
|
||||
{
|
||||
QDir::current().mkpath(m_instDir);
|
||||
}
|
||||
}
|
||||
|
||||
InstanceList::~InstanceList()
|
||||
{
|
||||
}
|
||||
|
||||
int InstanceList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return m_instances.count();
|
||||
}
|
||||
|
||||
QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
if (row < 0 || row >= m_instances.size())
|
||||
return QModelIndex();
|
||||
return createIndex(row, column, (void *)m_instances.at(row).get());
|
||||
}
|
||||
|
||||
QVariant InstanceList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
||||
switch (role)
|
||||
{
|
||||
case InstancePointerRole:
|
||||
{
|
||||
QVariant v = qVariantFromValue((void *)pdata);
|
||||
return v;
|
||||
}
|
||||
case InstanceIDRole:
|
||||
{
|
||||
return pdata->id();
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
return pdata->name();
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
return pdata->instanceRoot();
|
||||
}
|
||||
case Qt::DecorationRole:
|
||||
{
|
||||
return pdata->iconKey();
|
||||
}
|
||||
// HACK: see GroupView.h in gui!
|
||||
case GroupRole:
|
||||
{
|
||||
return pdata->group();
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags f;
|
||||
if (index.isValid())
|
||||
{
|
||||
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
void InstanceList::groupChanged()
|
||||
{
|
||||
// save the groups. save all of them.
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
QStringList InstanceList::getGroups()
|
||||
{
|
||||
return m_groups.toList();
|
||||
}
|
||||
|
||||
void InstanceList::suspendGroupSaving()
|
||||
{
|
||||
suspendedGroupSave = true;
|
||||
}
|
||||
|
||||
void InstanceList::resumeGroupSaving()
|
||||
{
|
||||
if(suspendedGroupSave)
|
||||
{
|
||||
suspendedGroupSave = false;
|
||||
if(queuedGroupSave)
|
||||
{
|
||||
saveGroupList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::deleteGroup(const QString& name)
|
||||
{
|
||||
for(auto & instance: m_instances)
|
||||
{
|
||||
auto instGroupName = instance->group();
|
||||
if(instGroupName == name)
|
||||
{
|
||||
instance->setGroupPost(QString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::saveGroupList()
|
||||
{
|
||||
if(suspendedGroupSave)
|
||||
{
|
||||
queuedGroupSave = true;
|
||||
return;
|
||||
}
|
||||
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
QMap<QString, QSet<QString>> groupMap;
|
||||
for (auto instance : m_instances)
|
||||
{
|
||||
QString id = instance->id();
|
||||
QString group = instance->group();
|
||||
if (group.isEmpty())
|
||||
continue;
|
||||
|
||||
// keep a list/set of groups for choosing
|
||||
m_groups.insert(group);
|
||||
|
||||
if (!groupMap.count(group))
|
||||
{
|
||||
QSet<QString> set;
|
||||
set.insert(id);
|
||||
groupMap[group] = set;
|
||||
}
|
||||
else
|
||||
{
|
||||
QSet<QString> &set = groupMap[group];
|
||||
set.insert(id);
|
||||
}
|
||||
}
|
||||
QJsonObject toplevel;
|
||||
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
||||
QJsonObject groupsArr;
|
||||
for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++)
|
||||
{
|
||||
auto list = iter.value();
|
||||
auto name = iter.key();
|
||||
QJsonObject groupObj;
|
||||
QJsonArray instanceArr;
|
||||
groupObj.insert("hidden", QJsonValue(QString("false")));
|
||||
for (auto item : list)
|
||||
{
|
||||
instanceArr.append(QJsonValue(item));
|
||||
}
|
||||
groupObj.insert("instances", instanceArr);
|
||||
groupsArr.insert(name, groupObj);
|
||||
}
|
||||
toplevel.insert("groups", groupsArr);
|
||||
QJsonDocument doc(toplevel);
|
||||
try
|
||||
{
|
||||
FS::write(groupFileName, doc.toJson());
|
||||
}
|
||||
catch(FS::FileSystemException & e)
|
||||
{
|
||||
qCritical() << "Failed to write instance group file :" << e.cause();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::loadGroupList(QMap<QString, QString> &groupMap)
|
||||
{
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
|
||||
// if there's no group file, fail
|
||||
if (!QFileInfo(groupFileName).exists())
|
||||
return;
|
||||
|
||||
QByteArray jsonData;
|
||||
try
|
||||
{
|
||||
jsonData = FS::read(groupFileName);
|
||||
}
|
||||
catch (FS::FileSystemException & e)
|
||||
{
|
||||
qCritical() << "Failed to read instance group file :" << e.cause();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
||||
|
||||
// if the json was bad, fail
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
||||
.arg(error.errorString(), QString::number(error.offset))
|
||||
.toUtf8();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the root of the json wasn't an object, fail
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
qWarning() << "Invalid group file. Root entry should be an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject rootObj = jsonDoc.object();
|
||||
|
||||
// Make sure the format version matches, otherwise fail.
|
||||
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
|
||||
return;
|
||||
|
||||
// Get the groups. if it's not an object, fail
|
||||
if (!rootObj.value("groups").isObject())
|
||||
{
|
||||
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate through all the groups.
|
||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
|
||||
{
|
||||
QString groupName = iter.key();
|
||||
|
||||
// If not an object, complain and skip to the next one.
|
||||
if (!iter.value().isObject())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list should "
|
||||
"be an object.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject groupObj = iter.value().toObject();
|
||||
if (!groupObj.value("instances").isArray())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list is invalid. "
|
||||
"It should contain an array "
|
||||
"called 'instances'.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep a list/set of groups for choosing
|
||||
m_groups.insert(groupName);
|
||||
|
||||
// Iterate through the list of instances in the group.
|
||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||
|
||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end();
|
||||
iter2++)
|
||||
{
|
||||
groupMap[(*iter2).toString()] = groupName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InstanceList::InstListError InstanceList::loadList()
|
||||
{
|
||||
// load the instance groups
|
||||
QMap<QString, QString> groupMap;
|
||||
loadGroupList(groupMap);
|
||||
|
||||
QList<InstancePtr> tempList;
|
||||
{
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
|
||||
QDirIterator::FollowSymlinks);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
QString subDir = iter.next();
|
||||
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
||||
continue;
|
||||
qDebug() << "Loading MultiMC instance from " << subDir;
|
||||
InstancePtr instPtr;
|
||||
auto error = loadInstance(instPtr, subDir);
|
||||
if(!continueProcessInstance(instPtr, error, subDir, groupMap))
|
||||
continue;
|
||||
tempList.append(instPtr);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: generalize
|
||||
FTBPlugin::loadInstances(m_globalSettings, groupMap, tempList);
|
||||
|
||||
beginResetModel();
|
||||
m_instances.clear();
|
||||
for(auto inst: tempList)
|
||||
{
|
||||
inst->setParent(this);
|
||||
connect(inst.get(), SIGNAL(propertiesChanged(BaseInstance *)), this,
|
||||
SLOT(propertiesChanged(BaseInstance *)));
|
||||
connect(inst.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged()));
|
||||
connect(inst.get(), SIGNAL(nuked(BaseInstance *)), this,
|
||||
SLOT(instanceNuked(BaseInstance *)));
|
||||
m_instances.append(inst);
|
||||
}
|
||||
endResetModel();
|
||||
emit dataIsInvalid();
|
||||
return NoError;
|
||||
}
|
||||
|
||||
/// Clear all instances. Triggers notifications.
|
||||
void InstanceList::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
saveGroupList();
|
||||
m_instances.clear();
|
||||
endResetModel();
|
||||
emit dataIsInvalid();
|
||||
}
|
||||
|
||||
void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
|
||||
{
|
||||
m_instDir = value.toString();
|
||||
loadList();
|
||||
}
|
||||
|
||||
/// Add an instance. Triggers notifications, returns the new index
|
||||
int InstanceList::add(InstancePtr t)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), m_instances.size(), m_instances.size());
|
||||
m_instances.append(t);
|
||||
t->setParent(this);
|
||||
connect(t.get(), SIGNAL(propertiesChanged(BaseInstance *)), this,
|
||||
SLOT(propertiesChanged(BaseInstance *)));
|
||||
connect(t.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged()));
|
||||
connect(t.get(), SIGNAL(nuked(BaseInstance *)), this, SLOT(instanceNuked(BaseInstance *)));
|
||||
endInsertRows();
|
||||
return count() - 1;
|
||||
}
|
||||
|
||||
InstancePtr InstanceList::getInstanceById(QString instId) const
|
||||
{
|
||||
if(instId.isEmpty())
|
||||
return InstancePtr();
|
||||
for(auto & inst: m_instances)
|
||||
{
|
||||
if (inst->id() == instId)
|
||||
{
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
return InstancePtr();
|
||||
}
|
||||
|
||||
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
|
||||
{
|
||||
return index(getInstIndex(getInstanceById(id).get()));
|
||||
}
|
||||
|
||||
int InstanceList::getInstIndex(BaseInstance *inst) const
|
||||
{
|
||||
int count = m_instances.count();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (inst == m_instances[i].get())
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool InstanceList::continueProcessInstance(InstancePtr instPtr, const int error,
|
||||
const QDir &dir, QMap<QString, QString> &groupMap)
|
||||
{
|
||||
if (error != InstanceList::NoLoadError && error != InstanceList::NotAnInstance)
|
||||
{
|
||||
QString errorMsg = QString("Failed to load instance %1: ")
|
||||
.arg(QFileInfo(dir.absolutePath()).baseName())
|
||||
.toUtf8();
|
||||
|
||||
switch (error)
|
||||
{
|
||||
default:
|
||||
errorMsg += QString("Unknown instance loader error %1").arg(error);
|
||||
break;
|
||||
}
|
||||
qCritical() << errorMsg.toUtf8();
|
||||
return false;
|
||||
}
|
||||
else if (!instPtr)
|
||||
{
|
||||
qCritical() << QString("Error loading instance %1. Instance loader returned null.")
|
||||
.arg(QFileInfo(dir.absolutePath()).baseName())
|
||||
.toUtf8();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto iter = groupMap.find(instPtr->id());
|
||||
if (iter != groupMap.end())
|
||||
{
|
||||
instPtr->setGroupInitial((*iter));
|
||||
}
|
||||
qDebug() << "Loaded instance " << instPtr->name() << " from " << dir.absolutePath();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
InstanceList::InstLoadError
|
||||
InstanceList::loadInstance(InstancePtr &inst, const QString &instDir)
|
||||
{
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instDir, "instance.cfg"));
|
||||
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
QString inst_type = instanceSettings->get("InstanceType").toString();
|
||||
|
||||
// FIXME: replace with a map lookup, where instance classes register their types
|
||||
if (inst_type == "OneSix" || inst_type == "Nostalgia")
|
||||
{
|
||||
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instDir));
|
||||
}
|
||||
else if (inst_type == "Legacy")
|
||||
{
|
||||
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instDir));
|
||||
}
|
||||
else
|
||||
{
|
||||
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instDir));
|
||||
}
|
||||
inst->init();
|
||||
return NoLoadError;
|
||||
}
|
||||
|
||||
InstanceList::InstCreateError
|
||||
InstanceList::createInstance(InstancePtr &inst, BaseVersionPtr version, const QString &instDir)
|
||||
{
|
||||
QDir rootDir(instDir);
|
||||
|
||||
qDebug() << instDir.toUtf8();
|
||||
if (!rootDir.exists() && !rootDir.mkpath("."))
|
||||
{
|
||||
qCritical() << "Can't create instance folder" << instDir;
|
||||
return InstanceList::CantCreateDir;
|
||||
}
|
||||
|
||||
if (!version)
|
||||
{
|
||||
qCritical() << "Can't create instance for non-existing MC version";
|
||||
return InstanceList::NoSuchVersion;
|
||||
}
|
||||
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instDir, "instance.cfg"));
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
auto minecraftVersion = std::dynamic_pointer_cast<MinecraftVersion>(version);
|
||||
if(minecraftVersion)
|
||||
{
|
||||
auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(version);
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instDir));
|
||||
inst->setIntendedVersionId(version->descriptor());
|
||||
inst->init();
|
||||
return InstanceList::NoCreateError;
|
||||
}
|
||||
return InstanceList::NoSuchVersion;
|
||||
}
|
||||
|
||||
InstanceList::InstCreateError
|
||||
InstanceList::copyInstance(InstancePtr &newInstance, InstancePtr &oldInstance, const QString &instDir, bool copySaves)
|
||||
{
|
||||
QDir rootDir(instDir);
|
||||
std::unique_ptr<IPathMatcher> matcher;
|
||||
if(!copySaves)
|
||||
{
|
||||
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
|
||||
matcherReal->caseSensitive(false);
|
||||
matcher.reset(matcherReal);
|
||||
}
|
||||
|
||||
qDebug() << instDir.toUtf8();
|
||||
FS::copy folderCopy(oldInstance->instanceRoot(), instDir);
|
||||
folderCopy.followSymlinks(false).blacklist(matcher.get());
|
||||
if (!folderCopy())
|
||||
{
|
||||
FS::deletePath(instDir);
|
||||
return InstanceList::CantCreateDir;
|
||||
}
|
||||
|
||||
INISettingsObject settings_obj(FS::PathCombine(instDir, "instance.cfg"));
|
||||
settings_obj.registerSetting("InstanceType", "Legacy");
|
||||
QString inst_type = settings_obj.get("InstanceType").toString();
|
||||
|
||||
oldInstance->copy(instDir);
|
||||
|
||||
auto error = loadInstance(newInstance, instDir);
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case NoLoadError:
|
||||
return NoCreateError;
|
||||
case NotAnInstance:
|
||||
rootDir.removeRecursively();
|
||||
return CantCreateDir;
|
||||
default:
|
||||
case UnknownLoadError:
|
||||
rootDir.removeRecursively();
|
||||
return UnknownCreateError;
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::instanceNuked(BaseInstance *inst)
|
||||
{
|
||||
int i = getInstIndex(inst);
|
||||
if (i != -1)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), i, i);
|
||||
m_instances.removeAt(i);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::propertiesChanged(BaseInstance *inst)
|
||||
{
|
||||
int i = getInstIndex(inst);
|
||||
if (i != -1)
|
||||
{
|
||||
emit dataChanged(index(i), index(i));
|
||||
}
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
/* Copyright 2013-2015 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 <QAbstractListModel>
|
||||
#include <QSet>
|
||||
|
||||
#include "BaseInstance.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class BaseInstance;
|
||||
class QDir;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
void loadGroupList(QMap<QString, QString> &groupList);
|
||||
void suspendGroupSaving();
|
||||
void resumeGroupSaving();
|
||||
|
||||
public slots:
|
||||
void saveGroupList();
|
||||
|
||||
public:
|
||||
explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
|
||||
virtual ~InstanceList();
|
||||
|
||||
public:
|
||||
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
|
||||
enum AdditionalRoles
|
||||
{
|
||||
GroupRole = Qt::UserRole,
|
||||
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
|
||||
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
|
||||
};
|
||||
/*!
|
||||
* \brief Error codes returned by functions in the InstanceList class.
|
||||
* NoError Indicates that no error occurred.
|
||||
* UnknownError indicates that an unspecified error occurred.
|
||||
*/
|
||||
enum InstListError
|
||||
{
|
||||
NoError = 0,
|
||||
UnknownError
|
||||
};
|
||||
|
||||
enum InstLoadError
|
||||
{
|
||||
NoLoadError = 0,
|
||||
UnknownLoadError,
|
||||
NotAnInstance
|
||||
};
|
||||
|
||||
enum InstCreateError
|
||||
{
|
||||
NoCreateError = 0,
|
||||
NoSuchVersion,
|
||||
UnknownCreateError,
|
||||
InstExists,
|
||||
CantCreateDir
|
||||
};
|
||||
|
||||
QString instDir() const
|
||||
{
|
||||
return m_instDir;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Get the instance at index
|
||||
*/
|
||||
InstancePtr at(int i) const
|
||||
{
|
||||
return m_instances.at(i);
|
||||
}
|
||||
;
|
||||
|
||||
/*!
|
||||
* \brief Get the count of loaded instances
|
||||
*/
|
||||
int count() const
|
||||
{
|
||||
return m_instances.count();
|
||||
}
|
||||
;
|
||||
|
||||
/// Clear all instances. Triggers notifications.
|
||||
void clear();
|
||||
|
||||
/// Add an instance. Triggers notifications, returns the new index
|
||||
int add(InstancePtr t);
|
||||
|
||||
/// Get an instance by ID
|
||||
InstancePtr getInstanceById(QString id) const;
|
||||
|
||||
QModelIndex getInstanceIndexById(const QString &id) const;
|
||||
|
||||
// FIXME: instead of iterating through all instances and forming a set, keep the set around
|
||||
QStringList getGroups();
|
||||
|
||||
void deleteGroup(const QString & name);
|
||||
|
||||
/*!
|
||||
* \brief Creates a stub instance
|
||||
*
|
||||
* \param inst Pointer to store the created instance in.
|
||||
* \param version Game version to use for the instance
|
||||
* \param instDir The new instance's directory.
|
||||
* \return An InstCreateError error code.
|
||||
* - InstExists if the given instance directory is already an instance.
|
||||
* - CantCreateDir if the given instance directory cannot be created.
|
||||
*/
|
||||
InstCreateError createInstance(InstancePtr &inst, BaseVersionPtr version,
|
||||
const QString &instDir);
|
||||
|
||||
/*!
|
||||
* \brief Creates a copy of an existing instance with a new name
|
||||
*
|
||||
* \param newInstance Pointer to store the created instance in.
|
||||
* \param oldInstance The instance to copy
|
||||
* \param instDir The new instance's directory.
|
||||
* \return An InstCreateError error code.
|
||||
* - InstExists if the given instance directory is already an instance.
|
||||
* - CantCreateDir if the given instance directory cannot be created.
|
||||
*/
|
||||
InstCreateError copyInstance(InstancePtr &newInstance, InstancePtr &oldInstance,
|
||||
const QString &instDir, bool copySaves);
|
||||
|
||||
/*!
|
||||
* \brief Loads an instance from the given directory.
|
||||
* Checks the instance's INI file to figure out what the instance's type is first.
|
||||
* \param inst Pointer to store the loaded instance in.
|
||||
* \param instDir The instance's directory.
|
||||
* \return An InstLoadError error code.
|
||||
* - NotAnInstance if the given instance directory isn't a valid instance.
|
||||
*/
|
||||
InstLoadError loadInstance(InstancePtr &inst, const QString &instDir);
|
||||
|
||||
signals:
|
||||
void dataIsInvalid();
|
||||
|
||||
public slots:
|
||||
void on_InstFolderChanged(const Setting &setting, QVariant value);
|
||||
|
||||
/*!
|
||||
* \brief Loads the instance list. Triggers notifications.
|
||||
*/
|
||||
InstListError loadList();
|
||||
|
||||
private slots:
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
void instanceNuked(BaseInstance *inst);
|
||||
void groupChanged();
|
||||
|
||||
private:
|
||||
int getInstIndex(BaseInstance *inst) const;
|
||||
|
||||
public:
|
||||
static bool continueProcessInstance(InstancePtr instPtr, const int error, const QDir &dir, QMap<QString, QString> &groupMap);
|
||||
|
||||
protected:
|
||||
QString m_instDir;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groups;
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
bool suspendedGroupSave = false;
|
||||
bool queuedGroupSave = false;
|
||||
};
|
@ -1,272 +0,0 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include <math.h>
|
||||
|
||||
namespace Json
|
||||
{
|
||||
void write(const QJsonDocument &doc, const QString &filename)
|
||||
{
|
||||
FS::write(filename, doc.toJson());
|
||||
}
|
||||
void write(const QJsonObject &object, const QString &filename)
|
||||
{
|
||||
write(QJsonDocument(object), filename);
|
||||
}
|
||||
void write(const QJsonArray &array, const QString &filename)
|
||||
{
|
||||
write(QJsonDocument(array), filename);
|
||||
}
|
||||
|
||||
QByteArray toBinary(const QJsonObject &obj)
|
||||
{
|
||||
return QJsonDocument(obj).toBinaryData();
|
||||
}
|
||||
QByteArray toBinary(const QJsonArray &array)
|
||||
{
|
||||
return QJsonDocument(array).toBinaryData();
|
||||
}
|
||||
QByteArray toText(const QJsonObject &obj)
|
||||
{
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
QByteArray toText(const QJsonArray &array)
|
||||
{
|
||||
return QJsonDocument(array).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
static bool isBinaryJson(const QByteArray &data)
|
||||
{
|
||||
decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag;
|
||||
return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0;
|
||||
}
|
||||
QJsonDocument requireDocument(const QByteArray &data, const QString &what)
|
||||
{
|
||||
if (isBinaryJson(data))
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromBinaryData(data);
|
||||
if (doc.isNull())
|
||||
{
|
||||
throw JsonException(what + ": Invalid JSON (binary JSON detected)");
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
else
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
throw JsonException(what + ": Error parsing JSON: " + error.errorString());
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
QJsonDocument requireDocument(const QString &filename, const QString &what)
|
||||
{
|
||||
return requireDocument(FS::read(filename), what);
|
||||
}
|
||||
QJsonObject requireObject(const QJsonDocument &doc, const QString &what)
|
||||
{
|
||||
if (!doc.isObject())
|
||||
{
|
||||
throw JsonException(what + " is not an object");
|
||||
}
|
||||
return doc.object();
|
||||
}
|
||||
QJsonArray requireArray(const QJsonDocument &doc, const QString &what)
|
||||
{
|
||||
if (!doc.isArray())
|
||||
{
|
||||
throw JsonException(what + " is not an array");
|
||||
}
|
||||
return doc.array();
|
||||
}
|
||||
|
||||
void writeString(QJsonObject &to, const QString &key, const QString &value)
|
||||
{
|
||||
if (!value.isEmpty())
|
||||
{
|
||||
to.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
void writeStringList(QJsonObject &to, const QString &key, const QStringList &values)
|
||||
{
|
||||
if (!values.isEmpty())
|
||||
{
|
||||
QJsonArray array;
|
||||
for(auto value: values)
|
||||
{
|
||||
array.append(value);
|
||||
}
|
||||
to.insert(key, array);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
QJsonValue toJson<QUrl>(const QUrl &url)
|
||||
{
|
||||
return QJsonValue(url.toString(QUrl::FullyEncoded));
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QByteArray>(const QByteArray &data)
|
||||
{
|
||||
return QJsonValue(QString::fromLatin1(data.toHex()));
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QDateTime>(const QDateTime &datetime)
|
||||
{
|
||||
return QJsonValue(datetime.toString(Qt::ISODate));
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QDir>(const QDir &dir)
|
||||
{
|
||||
return QDir::current().relativeFilePath(dir.absolutePath());
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QUuid>(const QUuid &uuid)
|
||||
{
|
||||
return uuid.toString();
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QVariant>(const QVariant &variant)
|
||||
{
|
||||
return QJsonValue::fromVariant(variant);
|
||||
}
|
||||
|
||||
|
||||
template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = ensureIsType<QString>(value, what);
|
||||
// ensure that the string can be safely cast to Latin1
|
||||
if (string != QString::fromLatin1(string.toLatin1()))
|
||||
{
|
||||
throw JsonException(what + " is not encodable as Latin1");
|
||||
}
|
||||
return QByteArray::fromHex(string.toLatin1());
|
||||
}
|
||||
|
||||
template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isArray())
|
||||
{
|
||||
throw JsonException(what + " is not an array");
|
||||
}
|
||||
return value.toArray();
|
||||
}
|
||||
|
||||
|
||||
template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isString())
|
||||
{
|
||||
throw JsonException(what + " is not a string");
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isBool())
|
||||
{
|
||||
throw JsonException(what + " is not a bool");
|
||||
}
|
||||
return value.toBool();
|
||||
}
|
||||
|
||||
template<> double requireIsType<double>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isDouble())
|
||||
{
|
||||
throw JsonException(what + " is not a double");
|
||||
}
|
||||
return value.toDouble();
|
||||
}
|
||||
|
||||
template<> int requireIsType<int>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const double doubl = requireIsType<double>(value, what);
|
||||
if (fmod(doubl, 1) != 0)
|
||||
{
|
||||
throw JsonException(what + " is not an integer");
|
||||
}
|
||||
return int(doubl);
|
||||
}
|
||||
|
||||
template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = requireIsType<QString>(value, what);
|
||||
const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate);
|
||||
if (!datetime.isValid())
|
||||
{
|
||||
throw JsonException(what + " is not a ISO formatted date/time value");
|
||||
}
|
||||
return datetime;
|
||||
}
|
||||
|
||||
template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = ensureIsType<QString>(value, what);
|
||||
if (string.isEmpty())
|
||||
{
|
||||
return QUrl();
|
||||
}
|
||||
const QUrl url = QUrl(string, QUrl::StrictMode);
|
||||
if (!url.isValid())
|
||||
{
|
||||
throw JsonException(what + " is not a correctly formatted URL");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = requireIsType<QString>(value, what);
|
||||
// FIXME: does not handle invalid characters!
|
||||
return QDir::current().absoluteFilePath(string);
|
||||
}
|
||||
|
||||
template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = requireIsType<QString>(value, what);
|
||||
const QUuid uuid = QUuid(string);
|
||||
if (uuid.toString() != string) // converts back => valid
|
||||
{
|
||||
throw JsonException(what + " is not a valid UUID");
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isObject())
|
||||
{
|
||||
throw JsonException(what + " is not an object");
|
||||
}
|
||||
return value.toObject();
|
||||
}
|
||||
|
||||
template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (value.isNull() || value.isUndefined())
|
||||
{
|
||||
throw JsonException(what + " is null or undefined");
|
||||
}
|
||||
return value.toVariant();
|
||||
}
|
||||
|
||||
template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (value.isNull() || value.isUndefined())
|
||||
{
|
||||
throw JsonException(what + " is null or undefined");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QDateTime>
|
||||
#include <QUrl>
|
||||
#include <QDir>
|
||||
#include <QUuid>
|
||||
#include <QVariant>
|
||||
#include <memory>
|
||||
|
||||
#include "Exception.h"
|
||||
|
||||
namespace Json
|
||||
{
|
||||
class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception
|
||||
{
|
||||
public:
|
||||
JsonException(const QString &message) : Exception(message) {}
|
||||
};
|
||||
|
||||
/// @throw FileSystemException
|
||||
void write(const QJsonDocument &doc, const QString &filename);
|
||||
/// @throw FileSystemException
|
||||
void write(const QJsonObject &object, const QString &filename);
|
||||
/// @throw FileSystemException
|
||||
void write(const QJsonArray &array, const QString &filename);
|
||||
|
||||
QByteArray toBinary(const QJsonObject &obj);
|
||||
QByteArray toBinary(const QJsonArray &array);
|
||||
QByteArray toText(const QJsonObject &obj);
|
||||
QByteArray toText(const QJsonArray &array);
|
||||
|
||||
/// @throw JsonException
|
||||
MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document");
|
||||
/// @throw JsonException
|
||||
MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QString &filename, const QString &what = "Document");
|
||||
/// @throw JsonException
|
||||
MULTIMC_LOGIC_EXPORT QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document");
|
||||
/// @throw JsonException
|
||||
MULTIMC_LOGIC_EXPORT QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document");
|
||||
|
||||
/////////////////// WRITING ////////////////////
|
||||
|
||||
void writeString(QJsonObject & to, const QString &key, const QString &value);
|
||||
void writeStringList(QJsonObject & to, const QString &key, const QStringList &values);
|
||||
|
||||
template<typename T>
|
||||
QJsonValue toJson(const T &t)
|
||||
{
|
||||
return QJsonValue(t);
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QUrl>(const QUrl &url);
|
||||
template<>
|
||||
QJsonValue toJson<QByteArray>(const QByteArray &data);
|
||||
template<>
|
||||
QJsonValue toJson<QDateTime>(const QDateTime &datetime);
|
||||
template<>
|
||||
QJsonValue toJson<QDir>(const QDir &dir);
|
||||
template<>
|
||||
QJsonValue toJson<QUuid>(const QUuid &uuid);
|
||||
template<>
|
||||
QJsonValue toJson<QVariant>(const QVariant &variant);
|
||||
|
||||
template<typename T>
|
||||
QJsonArray toJsonArray(const QList<T> &container)
|
||||
{
|
||||
QJsonArray array;
|
||||
for (const T item : container)
|
||||
{
|
||||
array.append(toJson<T>(item));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
////////////////// READING ////////////////////
|
||||
|
||||
/// @throw JsonException
|
||||
template <typename T>
|
||||
T requireIsType(const QJsonValue &value, const QString &what = "Value");
|
||||
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT double requireIsType<double>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT bool requireIsType<bool>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT int requireIsType<int>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QString requireIsType<QString>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QDir requireIsType<QDir>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what);
|
||||
|
||||
// the following functions are higher level functions, that make use of the above functions for
|
||||
// type conversion
|
||||
template <typename T>
|
||||
T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value")
|
||||
{
|
||||
if (value.isUndefined() || value.isNull())
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
try
|
||||
{
|
||||
return requireIsType<T>(value, what);
|
||||
}
|
||||
catch (JsonException &)
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
}
|
||||
|
||||
/// @throw JsonException
|
||||
template <typename T>
|
||||
T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
throw JsonException(localWhat + "s parent does not contain " + localWhat);
|
||||
}
|
||||
return requireIsType<T>(parent.value(key), localWhat);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
return ensureIsType<T>(parent.value(key), default_, localWhat);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> requireIsArrayOf(const QJsonDocument &doc)
|
||||
{
|
||||
const QJsonArray array = requireArray(doc);
|
||||
QVector<T> out;
|
||||
for (const QJsonValue val : array)
|
||||
{
|
||||
out.append(requireIsType<T>(val, "Document"));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value")
|
||||
{
|
||||
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
|
||||
QVector<T> out;
|
||||
for (const QJsonValue val : array)
|
||||
{
|
||||
out.append(requireIsType<T>(val, what));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value")
|
||||
{
|
||||
if (value.isUndefined())
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
return ensureIsArrayOf<T>(value, what);
|
||||
}
|
||||
|
||||
/// @throw JsonException
|
||||
template <typename T>
|
||||
QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
throw JsonException(localWhat + "s parent does not contain " + localWhat);
|
||||
}
|
||||
return ensureIsArrayOf<T>(parent.value(key), localWhat);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
|
||||
const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
return ensureIsArrayOf<T>(parent.value(key), default_, localWhat);
|
||||
}
|
||||
|
||||
// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers
|
||||
#define JSON_HELPERFUNCTIONS(NAME, TYPE) \
|
||||
inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \
|
||||
{ \
|
||||
return requireIsType<TYPE>(value, what); \
|
||||
} \
|
||||
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \
|
||||
{ \
|
||||
return ensureIsType<TYPE>(value, default_, what); \
|
||||
} \
|
||||
inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \
|
||||
{ \
|
||||
return requireIsType<TYPE>(parent, key, what); \
|
||||
} \
|
||||
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \
|
||||
{ \
|
||||
return ensureIsType<TYPE>(parent, key, default_, what); \
|
||||
}
|
||||
|
||||
JSON_HELPERFUNCTIONS(Array, QJsonArray)
|
||||
JSON_HELPERFUNCTIONS(Object, QJsonObject)
|
||||
JSON_HELPERFUNCTIONS(JsonValue, QJsonValue)
|
||||
JSON_HELPERFUNCTIONS(String, QString)
|
||||
JSON_HELPERFUNCTIONS(Boolean, bool)
|
||||
JSON_HELPERFUNCTIONS(Double, double)
|
||||
JSON_HELPERFUNCTIONS(Integer, int)
|
||||
JSON_HELPERFUNCTIONS(DateTime, QDateTime)
|
||||
JSON_HELPERFUNCTIONS(Url, QUrl)
|
||||
JSON_HELPERFUNCTIONS(ByteArray, QByteArray)
|
||||
JSON_HELPERFUNCTIONS(Dir, QDir)
|
||||
JSON_HELPERFUNCTIONS(Uuid, QUuid)
|
||||
JSON_HELPERFUNCTIONS(Variant, QVariant)
|
||||
|
||||
#undef JSON_HELPERFUNCTIONS
|
||||
|
||||
}
|
||||
using JSONValidationError = Json::JsonException;
|
@ -1,76 +0,0 @@
|
||||
#include "MMCStrings.h"
|
||||
|
||||
/// TAKEN FROM Qt, because it doesn't expose it intelligently
|
||||
static inline QChar getNextChar(const QString &s, int location)
|
||||
{
|
||||
return (location < s.length()) ? s.at(location) : QChar();
|
||||
}
|
||||
|
||||
/// TAKEN FROM Qt, because it doesn't expose it intelligently
|
||||
int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
|
||||
{
|
||||
for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
|
||||
{
|
||||
// skip spaces, tabs and 0's
|
||||
QChar c1 = getNextChar(s1, l1);
|
||||
while (c1.isSpace())
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
QChar c2 = getNextChar(s2, l2);
|
||||
while (c2.isSpace())
|
||||
c2 = getNextChar(s2, ++l2);
|
||||
|
||||
if (c1.isDigit() && c2.isDigit())
|
||||
{
|
||||
while (c1.digitValue() == 0)
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
while (c2.digitValue() == 0)
|
||||
c2 = getNextChar(s2, ++l2);
|
||||
|
||||
int lookAheadLocation1 = l1;
|
||||
int lookAheadLocation2 = l2;
|
||||
int currentReturnValue = 0;
|
||||
// find the last digit, setting currentReturnValue as we go if it isn't equal
|
||||
for (QChar lookAhead1 = c1, lookAhead2 = c2;
|
||||
(lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
|
||||
lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
|
||||
lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
|
||||
{
|
||||
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
|
||||
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
|
||||
if (!is1ADigit && !is2ADigit)
|
||||
break;
|
||||
if (!is1ADigit)
|
||||
return -1;
|
||||
if (!is2ADigit)
|
||||
return 1;
|
||||
if (currentReturnValue == 0)
|
||||
{
|
||||
if (lookAhead1 < lookAhead2)
|
||||
{
|
||||
currentReturnValue = -1;
|
||||
}
|
||||
else if (lookAhead1 > lookAhead2)
|
||||
{
|
||||
currentReturnValue = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentReturnValue != 0)
|
||||
return currentReturnValue;
|
||||
}
|
||||
if (cs == Qt::CaseInsensitive)
|
||||
{
|
||||
if (!c1.isLower())
|
||||
c1 = c1.toLower();
|
||||
if (!c2.isLower())
|
||||
c2 = c2.toLower();
|
||||
}
|
||||
int r = QString::localeAwareCompare(c1, c2);
|
||||
if (r < 0)
|
||||
return -1;
|
||||
if (r > 0)
|
||||
return 1;
|
||||
}
|
||||
// The two strings are the same (02 == 2) so fall back to the normal sort
|
||||
return QString::compare(s1, s2, cs);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
namespace Strings
|
||||
{
|
||||
int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
|
||||
}
|
@ -1,491 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2010 Roberto Pompermaier
|
||||
Copyright (C) 2005-2014 Sergey A. Tachenov
|
||||
|
||||
Parts of this file were part of QuaZIP.
|
||||
|
||||
QuaZIP 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.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QuaZIP 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 QuaZIP. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
See COPYING file for the full LGPL text.
|
||||
|
||||
Original ZIP package is copyrighted by Gilles Vollant and contributors,
|
||||
see quazip/(un)MMCZip.h files for details. Basically it's the zlib license.
|
||||
*/
|
||||
|
||||
#include <quazip.h>
|
||||
#include <JlCompress.h>
|
||||
#include <quazipdir.h>
|
||||
#include "MMCZip.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
bool 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;
|
||||
}
|
||||
|
||||
QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||
{
|
||||
return JlCompress::extractDir(fileCompressed, dir);
|
||||
}
|
||||
|
||||
bool compressFile(QuaZip *zip, QString fileName, QString fileDest)
|
||||
{
|
||||
if (!zip)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend &&
|
||||
zip->getMode() != QuaZip::mdAdd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile inFile;
|
||||
inFile.setFileName(fileName);
|
||||
if (!inFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QuaZipFile outFile(zip);
|
||||
if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!copyData(inFile, outFile) || outFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
outFile.close();
|
||||
if (outFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
inFile.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::compressSubDir(QuaZip* zip, QString dir, QString origDir, QSet<QString>& added, QString prefix, const SeparatorPrefixTree <'/'> * blacklist)
|
||||
{
|
||||
if (!zip) return false;
|
||||
if (zip->getMode()!=QuaZip::mdCreate && zip->getMode()!=QuaZip::mdAppend && zip->getMode()!=QuaZip::mdAdd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir directory(dir);
|
||||
if (!directory.exists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir origDirectory(origDir);
|
||||
if (dir != origDir)
|
||||
{
|
||||
QString internalDirName = origDirectory.relativeFilePath(dir);
|
||||
if(!blacklist || !blacklist->covers(internalDirName))
|
||||
{
|
||||
QuaZipFile dirZipFile(zip);
|
||||
auto dirPrefix = FS::PathCombine(prefix, origDirectory.relativeFilePath(dir)) + "/";
|
||||
if (!dirZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(dirPrefix, dir), 0, 0, 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
dirZipFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
QFileInfoList files = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
|
||||
for (auto file: files)
|
||||
{
|
||||
if(!file.isDir())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(!compressSubDir(zip,file.absoluteFilePath(),origDir, added, prefix, blacklist))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
files = directory.entryInfoList(QDir::Files);
|
||||
for (auto file: files)
|
||||
{
|
||||
if(!file.isFile())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(file.absoluteFilePath()==zip->getZipName())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString filename = origDirectory.relativeFilePath(file.absoluteFilePath());
|
||||
if(blacklist && blacklist->covers(filename))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(prefix.size())
|
||||
{
|
||||
filename = FS::PathCombine(prefix, filename);
|
||||
}
|
||||
added.insert(filename);
|
||||
if (!compressFile(zip,file.absoluteFilePath(),filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
|
||||
std::function<bool(QString)> filter)
|
||||
{
|
||||
QuaZip modZip(from.filePath());
|
||||
modZip.open(QuaZip::mdUnzip);
|
||||
|
||||
QuaZipFile fileInsideMod(&modZip);
|
||||
QuaZipFile zipOutFile(into);
|
||||
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
|
||||
{
|
||||
QString filename = modZip.getCurrentFileName();
|
||||
if (!filter(filename))
|
||||
{
|
||||
qDebug() << "Skipping file " << filename << " from "
|
||||
<< from.fileName() << " - filtered";
|
||||
continue;
|
||||
}
|
||||
if (contained.contains(filename))
|
||||
{
|
||||
qDebug() << "Skipping already contained file " << filename << " from "
|
||||
<< from.fileName();
|
||||
continue;
|
||||
}
|
||||
contained.insert(filename);
|
||||
|
||||
if (!fileInsideMod.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qCritical() << "Failed to open " << filename << " from " << from.fileName();
|
||||
return false;
|
||||
}
|
||||
|
||||
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
|
||||
|
||||
if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
|
||||
{
|
||||
qCritical() << "Failed to open " << filename << " in the jar";
|
||||
fileInsideMod.close();
|
||||
return false;
|
||||
}
|
||||
if (!copyData(fileInsideMod, zipOutFile))
|
||||
{
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
qCritical() << "Failed to copy data of " << filename << " into the jar";
|
||||
return false;
|
||||
}
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods)
|
||||
{
|
||||
QuaZip zipOut(targetJarPath);
|
||||
if (!zipOut.open(QuaZip::mdCreate))
|
||||
{
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to open the minecraft.jar for modding";
|
||||
return false;
|
||||
}
|
||||
// Files already added to the jar.
|
||||
// These files will be skipped.
|
||||
QSet<QString> addedFiles;
|
||||
|
||||
// Modify the jar
|
||||
QListIterator<Mod> i(mods);
|
||||
i.toBack();
|
||||
while (i.hasPrevious())
|
||||
{
|
||||
const Mod &mod = i.previous();
|
||||
// do not merge disabled mods.
|
||||
if (!mod.enabled())
|
||||
continue;
|
||||
if (mod.type() == Mod::MOD_ZIPFILE)
|
||||
{
|
||||
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles, noFilter))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_SINGLEFILE)
|
||||
{
|
||||
auto filename = mod.filename();
|
||||
if (!compressFile(&zipOut, filename.absoluteFilePath(),
|
||||
filename.fileName()))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
addedFiles.insert(filename.fileName());
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_FOLDER)
|
||||
{
|
||||
auto filename = mod.filename();
|
||||
QString what_to_zip = filename.absoluteFilePath();
|
||||
QDir dir(what_to_zip);
|
||||
dir.cdUp();
|
||||
QString parent_dir = dir.absolutePath();
|
||||
if (!compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Adding folder " << filename.fileName() << " from "
|
||||
<< filename.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, metaInfFilter))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to insert minecraft.jar contents.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recompress the jar
|
||||
zipOut.close();
|
||||
if (zipOut.getZipError() != 0)
|
||||
{
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to finalize minecraft.jar!";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::noFilter(QString)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::metaInfFilter(QString key)
|
||||
{
|
||||
if(key.contains("META-INF"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::compressDir(QString zipFile, QString dir, QString prefix, const SeparatorPrefixTree <'/'> * blacklist)
|
||||
{
|
||||
QuaZip zip(zipFile);
|
||||
QDir().mkpath(QFileInfo(zipFile).absolutePath());
|
||||
if(!zip.open(QuaZip::mdCreate))
|
||||
{
|
||||
QFile::remove(zipFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
QSet<QString> added;
|
||||
if (!compressSubDir(&zip, dir, dir, added, prefix, blacklist))
|
||||
{
|
||||
QFile::remove(zipFile);
|
||||
return false;
|
||||
}
|
||||
zip.close();
|
||||
if(zip.getZipError()!=0)
|
||||
{
|
||||
QFile::remove(zipFile);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString &root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
{
|
||||
if(fileName == what)
|
||||
return root;
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
QString result = findFileInZip(zip, what, root + fileName);
|
||||
if(!result.isEmpty())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
{
|
||||
if(fileName == what)
|
||||
{
|
||||
result.append(root);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
findFilesInZip(zip, what, result, root + fileName);
|
||||
}
|
||||
return !result.isEmpty();
|
||||
}
|
||||
|
||||
bool removeFile(QStringList listFile)
|
||||
{
|
||||
bool ret = true;
|
||||
for (int i = 0; i < listFile.count(); i++)
|
||||
{
|
||||
ret &= QFile::remove(listFile.at(i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MMCZip::extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest)
|
||||
{
|
||||
if(!zip)
|
||||
return false;
|
||||
|
||||
if (zip->getMode() != QuaZip::mdUnzip)
|
||||
return false;
|
||||
|
||||
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 (fileDest.endsWith('/'))
|
||||
{
|
||||
if (!curDir.mkpath(fileDest))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!curDir.mkpath(QFileInfo(fileDest).absolutePath()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QuaZipFileInfo64 info;
|
||||
if (!zip->getCurrentFileInfo(&info))
|
||||
return false;
|
||||
|
||||
QFile::Permissions srcPerm = info.getPermissions();
|
||||
if (fileDest.endsWith('/') && QFileInfo(fileDest).isDir())
|
||||
{
|
||||
if (srcPerm != 0)
|
||||
{
|
||||
QFile(fileDest).setPermissions(srcPerm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QFile outFile;
|
||||
outFile.setFileName(fileDest);
|
||||
if (!outFile.open(QIODevice::WriteOnly))
|
||||
return false;
|
||||
|
||||
if (!copyData(inFile, outFile) || inFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
outFile.close();
|
||||
removeFile(QStringList(fileDest));
|
||||
return false;
|
||||
}
|
||||
outFile.close();
|
||||
|
||||
inFile.close();
|
||||
if (inFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
removeFile(QStringList(fileDest));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (srcPerm != 0)
|
||||
{
|
||||
outFile.setPermissions(srcPerm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
|
||||
{
|
||||
QDir directory(target);
|
||||
QStringList extracted;
|
||||
if (!zip->goToFirstFile())
|
||||
{
|
||||
return QStringList();
|
||||
}
|
||||
do
|
||||
{
|
||||
QString name = zip->getCurrentFileName();
|
||||
if(!name.startsWith(subdir))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
name.remove(0, subdir.size());
|
||||
QString absFilePath = directory.absoluteFilePath(name);
|
||||
if(name.isEmpty())
|
||||
{
|
||||
absFilePath += "/";
|
||||
}
|
||||
if (!extractFile(zip, "", absFilePath))
|
||||
{
|
||||
removeFile(extracted);
|
||||
return QStringList();
|
||||
}
|
||||
extracted.append(absFilePath);
|
||||
} while (zip->goToNextFile());
|
||||
return extracted;
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QFileInfo>
|
||||
#include <QSet>
|
||||
#include "minecraft/Mod.h"
|
||||
#include "SeparatorPrefixTree.h"
|
||||
#include <functional>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QuaZip;
|
||||
|
||||
namespace MMCZip
|
||||
{
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT compressSubDir(QuaZip *zip, QString dir, QString origDir, QSet<QString> &added,
|
||||
QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT compressDir(QString zipFile, QString dir, QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
|
||||
|
||||
/// filter function for @mergeZipFiles - passthrough
|
||||
bool MULTIMC_LOGIC_EXPORT noFilter(QString key);
|
||||
|
||||
/// filter function for @mergeZipFiles - ignores METAINF
|
||||
bool MULTIMC_LOGIC_EXPORT metaInfFilter(QString key);
|
||||
|
||||
/**
|
||||
* Merge two zip files, using a filter function
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, std::function<bool(QString)> filter);
|
||||
|
||||
/**
|
||||
* take a source jar, add mods to it, resulting in target jar
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir = QString());
|
||||
|
||||
/**
|
||||
* Find a single file in archive by file name (not path)
|
||||
*
|
||||
* \return the path prefix where the file is
|
||||
*/
|
||||
QString MULTIMC_LOGIC_EXPORT findFileInZip(QuaZip * zip, const QString & what, const QString &root = QString());
|
||||
|
||||
/**
|
||||
* Find a multiple files of the same name in archive by file name
|
||||
* If a file is found in a path, no deeper paths are searched
|
||||
*
|
||||
* \return true if anything was found
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
|
||||
|
||||
/**
|
||||
* Extract a single file to a destination
|
||||
*
|
||||
* \return true if it succeeds
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest);
|
||||
|
||||
/**
|
||||
* Extract a subdirectory from an archive
|
||||
*/
|
||||
QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
#pragma once
|
||||
#include "BaseInstance.h"
|
||||
|
||||
class NullInstance: public BaseInstance
|
||||
{
|
||||
public:
|
||||
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
|
||||
:BaseInstance(globalSettings, settings, rootDir)
|
||||
{
|
||||
setFlag(BaseInstance::VersionBrokenFlag);
|
||||
}
|
||||
virtual ~NullInstance() {};
|
||||
virtual bool setIntendedVersionId(QString) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual void cleanupAfterRun() override
|
||||
{
|
||||
}
|
||||
virtual QString currentVersionId() const override
|
||||
{
|
||||
return "Null";
|
||||
};
|
||||
virtual QString intendedVersionId() const override
|
||||
{
|
||||
return "Null";
|
||||
};
|
||||
virtual void init() override
|
||||
{
|
||||
};
|
||||
virtual QString getStatusbarDescription() override
|
||||
{
|
||||
return tr("Unknown instance type");
|
||||
};
|
||||
virtual bool shouldUpdate() const override
|
||||
{
|
||||
return false;
|
||||
};
|
||||
virtual QSet< QString > traits() override
|
||||
{
|
||||
return {};
|
||||
};
|
||||
virtual QString instanceConfigFolder() const override
|
||||
{
|
||||
return instanceRoot();
|
||||
};
|
||||
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual std::shared_ptr< Task > createUpdateTask() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual std::shared_ptr<Task> createJarModdingTask() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual void setShouldUpdate(bool) override
|
||||
{
|
||||
};
|
||||
virtual std::shared_ptr< BaseVersionList > versionList() const override
|
||||
{
|
||||
return nullptr;
|
||||
};
|
||||
virtual QProcessEnvironment createEnvironment() override
|
||||
{
|
||||
return QProcessEnvironment();
|
||||
}
|
||||
virtual QMap<QString, QString> getVariables() const override
|
||||
{
|
||||
return QMap<QString, QString>();
|
||||
}
|
||||
virtual IPathMatcher::Ptr getLogFileMatcher() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual QString getLogFileRoot() override
|
||||
{
|
||||
return instanceRoot();
|
||||
}
|
||||
virtual QString typeName() const override
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
bool canExport() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QObject>
|
||||
|
||||
namespace details
|
||||
{
|
||||
struct DeleteQObjectLater
|
||||
{
|
||||
void operator()(QObject *obj) const
|
||||
{
|
||||
obj->deleteLater();
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* A unique pointer class with unique pointer semantics intended for derivates of QObject
|
||||
* Calls deleteLater() instead of destroying the contained object immediately
|
||||
*/
|
||||
template<typename T> using unique_qobject_ptr = std::unique_ptr<T, details::DeleteQObjectLater>;
|
||||
|
||||
/**
|
||||
* A shared pointer class with shared pointer semantics intended for derivates of QObject
|
||||
* Calls deleteLater() instead of destroying the contained object immediately
|
||||
*/
|
||||
template <typename T>
|
||||
class shared_qobject_ptr
|
||||
{
|
||||
public:
|
||||
shared_qobject_ptr(){}
|
||||
shared_qobject_ptr(T * wrap)
|
||||
{
|
||||
reset(wrap);
|
||||
}
|
||||
shared_qobject_ptr(const shared_qobject_ptr<T>& other)
|
||||
{
|
||||
m_ptr = other.m_ptr;
|
||||
}
|
||||
template<typename Derived>
|
||||
shared_qobject_ptr(const shared_qobject_ptr<Derived> &other)
|
||||
{
|
||||
m_ptr = other.unwrap();
|
||||
}
|
||||
|
||||
public:
|
||||
void reset(T * wrap)
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1));
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
m_ptr.reset();
|
||||
}
|
||||
T * get() const
|
||||
{
|
||||
return m_ptr.get();
|
||||
}
|
||||
T * operator->() const
|
||||
{
|
||||
return m_ptr.get();
|
||||
}
|
||||
T & operator*() const
|
||||
{
|
||||
return *m_ptr.get();
|
||||
}
|
||||
operator bool() const
|
||||
{
|
||||
return m_ptr.get() != nullptr;
|
||||
}
|
||||
const std::shared_ptr <T> unwrap() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr <T> m_ptr;
|
||||
};
|
@ -1,60 +0,0 @@
|
||||
#pragma once
|
||||
template <typename K, typename V>
|
||||
class RWStorage
|
||||
{
|
||||
public:
|
||||
void add(K key, V value)
|
||||
{
|
||||
QWriteLocker l(&lock);
|
||||
cache[key] = value;
|
||||
stale_entries.remove(key);
|
||||
}
|
||||
V get(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
return cache[key];
|
||||
}
|
||||
else return V();
|
||||
}
|
||||
bool get(K key, V& value)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
value = cache[key];
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
bool has(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
return cache.contains(key);
|
||||
}
|
||||
bool stale(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(!cache.contains(key))
|
||||
return true;
|
||||
return stale_entries.contains(key);
|
||||
}
|
||||
void setStale(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
stale_entries.insert(key);
|
||||
}
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
QWriteLocker l(&lock);
|
||||
cache.clear();
|
||||
}
|
||||
private:
|
||||
QReadWriteLock lock;
|
||||
QMap<K, V> cache;
|
||||
QSet<K> stale_entries;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user