diff options
166 files changed, 22893 insertions, 3027 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..167a8fa7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,24 @@ +UseTab: true +IndentWidth: 4 +TabWidth: 4 +ConstructorInitializerIndentWidth: 4 +AccessModifierOffset: -4 +IndentCaseLabels: false +IndentFunctionDeclarationAfterType: false +NamespaceIndentation: None + +BreakBeforeBraces: Allman +AllowShortIfStatementsOnASingleLine: false +ColumnLimit: 96 +MaxEmptyLinesToKeep: 1 + +Standard: Cpp11 +Cpp11BracedListStyle: true + +SpacesInParentheses: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true + +AlignTrailingComments: true +SpacesBeforeTrailingComments: 1 @@ -4,6 +4,7 @@ MultiMC5.kdev4 MultiMC.pro.user CMakeLists.txt.user .user +.directory build resources/CMakeFiles resources/MultiMCLauncher.jar diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 00000000..e2c17e64 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,115 @@ +Build Instructions +================== + +# Contents +* [Linux](#linux) +* [Windows](#windows) +* [OS X](#os-x) + +# Linux + +## Dependencies + +* Qt 5.1.1+ Development tools (http://qt-project.org/downloads) +* cmake +* ccmake +* make +* g++ +* A copy of the MultiMC source (clone it with git) + +## Getting set up + +```bash +git clone git@github.com:MultiMC/MultiMC5.git # get the code +cd MultiMC5 +git checkout develop +``` + +once that is done, do these commands: + +```bash +mkdir build +cd build +ccmake .. +``` + +A GUI will pop up. press the c key. now set the build prefix. if you are in /home/username/code/MultiMC5/build then put /home/username/code/MultiMC5/build/run as build prefix. if you want you can choose whatever dir you want, but then you need to adjust the path when running it. to edit the value use the up/down keys to select it and hit return to edit it. after you are done hit return again. +Also adjust the paths to your qt install. + +Then hit c and g. If the window stays open (and g has no effect) retry c followed by g. sometimes you need to use c twice. +If you get an error make sure you have all dependencies installed and configured the paths properly + +continue with the following commands: + +```bash +cmake .. +make +make translations_target # compiles localization files. you may leave this out if your language is english +make install +``` +now you compiled it (hupefully) successfully. + +to launch it: + +```bash +cd run # or whereever its stored +./MultiMC5 +``` + +Congrats. Your MMC5 should run + +# Windows + +Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using VC's build tools as it uses some C++11 features that aren't implemented in it at the time of writing. + +## Dependencies +* Qt 5.1.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Windows") +* OpenSSL (http://slproweb.com/products/Win32OpenSSL.html) ("Win32 OpenSSL v1.0.1e Light") + - Microsoft Visual C++ 2008 Redist. is required for this, there's a link on the OpenSSL download page above next to the main download. +* CMake (http://www.cmake.org/cmake/resources/software.html) ("Windows (Win32 Installer)") +* A copy of the MultiMC source (clone it with git) + +## Getting set up + +### Installing Qt +1. Run the Qt installer +2. Choose a place to install Qt (C:\Qt is the default), +3. Choose the components you want to install + - You need Qt 5.1.1/MinGW 4.8 (32 bit) ticked, + - You need Tools/Qt Creator ticked, + - Other components are selected by default, you can untick them if you don't need them. +4. Accept the license agreements, +5. Double check the install details and then click "Install" + - Installation can take a very long time, go grab a cup of tea or something and let it work. + +### Installing OpenSSL +1. Run the OpenSSL installer, +2. It's best to choose the option to copy OpenSSL DLLs to the /bin directory + - If you do this you'll need to add that directory (the default being C:\OpenSSL-Win32\bin) to your PATH system variable (Google how to do this, or use this guide for Java: http://www.java.com/en/download/help/path.xml). + +### Installing CMake +1. Run the CMake installer, +2. It's easiest if you choose to add CMake to the PATH for all users, + - If you don't choose to do this, remember where you installed CMake. + +### Loading the project +1. Open Qt Creator, +2. Choose File->Open File or Project, +3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt, +4. Read the instructions that just popped up about a build location and choose one, +5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it, + - Otherwise you can skip this step. +6. You should see "Run CMake" in the window, + - Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.1.1 MinGW 32bit)", + - Hit the "Run CMake" button, + - You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window. + - Hit "Finish" if CMake ran successfully. +7. Cross your fingers and press the Run button (bottom left of Qt Creator)! + - If the project builds successfully it will run and the MultiMC5 window will pop up, + - Test OpenSSL by making an instance and trying to log in. If Qt Creator couldn't find OpenSSL during the CMake stage, login will fail and you'll get an error. + +*These build instructions worked for me (Drayshak) on a fresh Windows 8 x64 Professional install. If they don't work for you, let us know on IRC (Esper/#MultiMC)!* + +# OS X + +*There are no build instructions for OS X yet. If you can help with this section please contact us on IRC (Esper/#MultiMC)!*
\ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 27f8eaba..b7dd6ea3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,16 @@ cmake_minimum_required(VERSION 2.8.9) + +IF(WIN32) + # In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows + cmake_policy(SET CMP0020 OLD) +ENDIF() + project(MultiMC) ######## Set CMake options ######## SET(CMAKE_AUTOMOC ON) SET(CMAKE_INCLUDE_CURRENT_DIR ON) +SET(FILES_TO_TRANSLATE ) # Output all executables and shared libs in the main build folder, not in subfolders. SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) @@ -32,6 +39,7 @@ ENDIF() # Find the required Qt parts find_package(Qt5Widgets REQUIRED) find_package(Qt5Network REQUIRED) +find_package(Qt5LinguistTools REQUIRED) include_directories(${Qt5Widgets_INCLUDE_DIRS}) @@ -44,6 +52,14 @@ include_directories(depends/quazip) # Add the java launcher add_subdirectory(depends/launcher) +# Add xz decompression +add_subdirectory(depends/xz-embedded) +include_directories(${XZ_INCLUDE_DIR}) + +# Add pack200 decompression +add_subdirectory(depends/pack200) +include_directories(${PACK200_INCLUDE_DIR}) + ######## MultiMC Libs ######## # Add the util library. @@ -58,7 +74,6 @@ include_directories(${LIBSETTINGS_INCLUDE_DIR}) add_subdirectory(depends/groupview) include_directories(${LIBGROUPVIEW_INCLUDE_DIR}) - ################################ SET UP BUILD OPTIONS ################################ ######## Check endianness ######## @@ -78,10 +93,13 @@ SET(MultiMC_VERSION_MAJOR 5) SET(MultiMC_VERSION_MINOR 0) SET(MultiMC_VERSION_REV 0) -# Jenkins build number +# Build number SET(MultiMC_VERSION_BUILD 0 CACHE STRING "Build number.") MESSAGE(STATUS "MultiMC build #${MultiMC_VERSION_BUILD}") +# Custom target to just print the version. +ADD_CUSTOM_TARGET(version echo "Version: ${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_REV}.${MultiMC_VERSION_BUILD}") + # Check the current Git commit execute_process(COMMAND git rev-parse HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} @@ -145,141 +163,161 @@ ADD_DEFINITIONS(-DLIBGROUPVIEW_STATIC) ################################ FILES ################################ -######## Headers ######## -SET(MULTIMC_HEADERS +######## Sources and headers ######## +SET(MULTIMC_SOURCES +# Application base MultiMC.h +MultiMC.cpp MultiMCVersion.h +# Logging +logger/QsDebugOutput.cpp +logger/QsDebugOutput.h +logger/QsLog.cpp +logger/QsLog.h +logger/QsLogDest.cpp +logger/QsLogDest.h + +# GUI gui/mainwindow.h +gui/mainwindow.cpp gui/settingsdialog.h +gui/settingsdialog.cpp gui/newinstancedialog.h +gui/newinstancedialog.cpp gui/logindialog.h -gui/taskdialog.h +gui/logindialog.cpp +gui/ProgressDialog.h +gui/ProgressDialog.cpp gui/aboutdialog.h +gui/aboutdialog.cpp gui/consolewindow.h +gui/consolewindow.cpp gui/instancedelegate.h +gui/instancedelegate.cpp gui/versionselectdialog.h +gui/versionselectdialog.cpp gui/lwjglselectdialog.h +gui/lwjglselectdialog.cpp gui/instancesettings.h +gui/instancesettings.cpp gui/IconPickerDialog.h +gui/IconPickerDialog.cpp gui/LegacyModEditDialog.h +gui/LegacyModEditDialog.cpp gui/OneSixModEditDialog.h +gui/OneSixModEditDialog.cpp gui/ModEditDialogCommon.h +gui/ModEditDialogCommon.cpp gui/ModListView.h +gui/ModListView.cpp gui/LabeledToolButton.h +gui/LabeledToolButton.cpp gui/EditNotesDialog.h +gui/EditNotesDialog.cpp +gui/MCModInfoFrame.h +gui/MCModInfoFrame.cpp # Base classes and infrastructure -logic/InstanceVersion.h +logic/BaseVersion.h logic/MinecraftVersion.h logic/InstanceFactory.h +logic/InstanceFactory.cpp logic/BaseUpdate.h +logic/BaseUpdate.cpp logic/BaseInstance.h +logic/BaseInstance.cpp logic/BaseInstance_p.h + logic/MinecraftProcess.h +logic/MinecraftProcess.cpp logic/Mod.h +logic/Mod.cpp logic/ModList.h +logic/ModList.cpp # Basic instance launcher for starting from terminal logic/InstanceLauncher.h +logic/InstanceLauncher.cpp # network stuffs +logic/net/Download.h +logic/net/FileDownload.h +logic/net/FileDownload.cpp +logic/net/ByteArrayDownload.h +logic/net/ByteArrayDownload.cpp +logic/net/CacheDownload.h +logic/net/CacheDownload.cpp +logic/net/ForgeXzDownload.h +logic/net/ForgeXzDownload.cpp logic/net/DownloadJob.h +logic/net/DownloadJob.cpp logic/net/HttpMetaCache.h +logic/net/HttpMetaCache.cpp +logic/net/LoginTask.h +logic/net/LoginTask.cpp # legacy instances logic/LegacyInstance.h +logic/LegacyInstance.cpp logic/LegacyInstance_p.h logic/LegacyUpdate.h +logic/LegacyUpdate.cpp logic/LegacyForge.h +logic/LegacyForge.cpp # 1.6 instances logic/OneSixAssets.h +logic/OneSixAssets.cpp logic/OneSixInstance.h +logic/OneSixInstance.cpp logic/OneSixInstance_p.h logic/OneSixUpdate.h +logic/OneSixUpdate.cpp logic/OneSixVersion.h -logic/VersionFactory.h +logic/OneSixVersion.cpp +logic/OneSixLibrary.h +logic/OneSixLibrary.cpp +logic/OneSixRule.h +logic/OneSixRule.cpp +logic/OpSys.h +logic/OpSys.cpp +logic/ForgeInstaller.h +logic/ForgeInstaller.cpp # Nostalgia logic/NostalgiaInstance.h +logic/NostalgiaInstance.cpp # Lists logic/lists/InstanceList.h -logic/lists/InstVersionList.h +logic/lists/InstanceList.cpp +logic/lists/IconList.h +logic/lists/IconList.cpp +logic/lists/BaseVersionList.h +logic/lists/BaseVersionList.cpp logic/lists/MinecraftVersionList.h +logic/lists/MinecraftVersionList.cpp logic/lists/LwjglVersionList.h -logic/lists/IconList.h +logic/lists/LwjglVersionList.cpp +logic/lists/ForgeVersionList.h +logic/lists/ForgeVersionList.cpp +logic/lists/JavaVersionList.h +logic/lists/JavaVersionList.cpp + +# misc model/view +logic/EnabledItemFilter.h +logic/EnabledItemFilter.cpp # Tasks +logic/tasks/ProgressProvider.h logic/tasks/Task.h -logic/tasks/LoginTask.h -) - - -######## Sources ######## -SET(MULTIMC_SOURCES -MultiMC.cpp - -gui/mainwindow.cpp -gui/settingsdialog.cpp -gui/newinstancedialog.cpp -gui/logindialog.cpp -gui/taskdialog.cpp -gui/aboutdialog.cpp -gui/consolewindow.cpp -gui/instancedelegate.cpp -gui/versionselectdialog.cpp -gui/lwjglselectdialog.cpp -gui/instancesettings.cpp -gui/IconPickerDialog.cpp -gui/LegacyModEditDialog.cpp -gui/OneSixModEditDialog.cpp -gui/ModEditDialogCommon.cpp -gui/ModListView.cpp -gui/LabeledToolButton.cpp -gui/EditNotesDialog.cpp - -# Base classes and infrastructure -logic/InstanceFactory.cpp -logic/BaseUpdate.cpp -logic/BaseInstance.cpp -logic/MinecraftProcess.cpp -logic/Mod.cpp -logic/ModList.cpp - -# Basic instance launcher for starting from terminal -logic/InstanceLauncher.cpp - -# network stuffs - to be moved into a depend lib ~_~ -logic/net/DownloadJob.cpp -logic/net/HttpMetaCache.cpp - -# legacy instances -logic/LegacyInstance.cpp -logic/LegacyUpdate.cpp -logic/LegacyForge.cpp - -# 1.6 instances -logic/OneSixAssets.cpp -logic/OneSixInstance.cpp -logic/OneSixVersion.cpp -logic/OneSixUpdate.cpp -logic/VersionFactory.cpp - -# Nostalgia -logic/NostalgiaInstance.cpp +logic/tasks/Task.cpp -# Lists -logic/lists/InstanceList.cpp -logic/lists/InstVersionList.cpp -logic/lists/MinecraftVersionList.cpp -logic/lists/LwjglVersionList.cpp -logic/lists/IconList.cpp +# Utilities +logic/JavaUtils.h +logic/JavaUtils.cpp -# Tasks -logic/tasks/Task.cpp -logic/tasks/LoginTask.cpp ) @@ -289,24 +327,38 @@ gui/mainwindow.ui gui/settingsdialog.ui gui/newinstancedialog.ui gui/logindialog.ui -gui/taskdialog.ui gui/aboutdialog.ui gui/consolewindow.ui gui/versionselectdialog.ui gui/lwjglselectdialog.ui gui/instancesettings.ui + +gui/ProgressDialog.ui gui/IconPickerDialog.ui gui/LegacyModEditDialog.ui gui/OneSixModEditDialog.ui gui/EditNotesDialog.ui + +gui/MCModInfoFrame.ui ) +set (FILES_TO_TRANSLATE ${FILES_TO_TRANSLATE} ${MULTIMC_SOURCES} ${MULTIMC_UIS}) + ######## Windows resource files ######## IF(WIN32) SET(MULTIMC_RCS multimc.rc) ENDIF() +####### X11 Stuff ####### +IF(UNIX AND NOT APPLE) + SET(MultiMC_QT_ADDITIONAL_MODULES ${MultiMC_QT_ADDITIONAL_MODULES} X11Extras) + SET(MultiMC_LINK_ADDITIONAL_LIBS ${MultiMC_LINK_ADDITIONAL_LIBS} xcb) + LIST(APPEND MULTIMC_SOURCES gui/platform_x11.cpp) +ELSE() + LIST(APPEND MULTIMC_SOURCES gui/platform_other.cpp) +ENDIF() + ################################ COMPILE ################################ @@ -333,13 +385,12 @@ QT5_ADD_RESOURCES(MULTIMC_QRC multimc.qrc) # Add executable ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 - ${MULTIMC_SOURCES} ${MULTIMC_HEADERS} ${MULTIMC_UI} ${MULTIMC_QRC} ${MULTIMC_RCS}) + ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_QRC} ${MULTIMC_RCS}) # Link -QT5_USE_MODULES(MultiMC Widgets Network Xml) -TARGET_LINK_LIBRARIES(MultiMC quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS}) -ADD_DEPENDENCIES(MultiMC MultiMCLauncher libUtil libSettings libGroupView) - +TARGET_LINK_LIBRARIES(MultiMC xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS}) +QT5_USE_MODULES(MultiMC Core Widgets Network Xml ${MultiMC_QT_ADDITIONAL_MODULES}) +ADD_DEPENDENCIES(MultiMC MultiMCLauncher) option(BUILD_KEYRING_TEST "Build the simple keyring test binary" OFF) IF(BUILD_KEYRING_TEST) @@ -357,9 +408,9 @@ ENDIF() ######## Plugin and library folders ######## -SET(PLUGIN_DEST_DIR bin) -SET(QTCONF_DEST_DIR bin) -SET(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC") +SET(PLUGIN_DEST_DIR plugins) +SET(QTCONF_DEST_DIR .) +SET(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC") IF(WIN32) SET(PLUGIN_DEST_DIR .) @@ -367,10 +418,14 @@ IF(WIN32) SET(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe") ENDIF() +IF(UNIX) IF(APPLE) SET(PLUGIN_DEST_DIR MultiMC.app/Contents/MacOS) SET(QTCONF_DEST_DIR MultiMC.app/Contents/Resources) SET(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app") +ELSE() + SET(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC") +ENDIF() ENDIF() SET(QT_PLUGINS_DIR ${Qt5_DIR}/plugins) @@ -394,6 +449,7 @@ ENDIF(APPLE) IF(WIN32) INSTALL(TARGETS MultiMC BUNDLE DESTINATION . COMPONENT Runtime + LIBRARY DESTINATION . COMPONENT Runtime RUNTIME DESTINATION . COMPONENT Runtime ) ENDIF() @@ -406,35 +462,40 @@ INSTALL(TARGETS MultiMC ELSE() INSTALL(TARGETS MultiMC BUNDLE DESTINATION . COMPONENT Runtime - RUNTIME DESTINATION . COMPONENT Runtime + RUNTIME DESTINATION bin COMPONENT Runtime ) +INSTALL(PROGRAMS package/linux/MultiMC DESTINATION .) ENDIF() ENDIF() #### Plugins #### -# # Image formats -# INSTALL(DIRECTORY "${QT_PLUGINS_DIR}/imageformats" DESTINATION ${PLUGIN_DEST_DIR} COMPONENT Runtime) -# -# # Platform plugins -# INSTALL(DIRECTORY "${QT_PLUGINS_DIR}/platforms" DESTINATION ${PLUGIN_DEST_DIR} COMPONENT Runtime) -# -# # qtconf -# INSTALL(CODE " -# FILE(WRITE \"\${CMAKE_INSTALL_PREFIX}/${QTCONF_DEST_DIR}/qt.conf\" \"\") -# " COMPONENT Runtime) -# -# -# # Dirs to look for dependencies. -# SET(DIRS "${QT_LIBRARY_DIRS}") -# -# INSTALL(CODE " -# file(GLOB_RECURSE QTPLUGINS -# \"\${CMAKE_INSTALL_PREFIX}/${PLUGIN_DEST_DIR}/plugins/*${CMAKE_SHARED_LIBRARY_SUFFIX}\") -# include(BundleUtilities) -# fixup_bundle(\"${APPS}\" \"\${QTPLUGINS}\" \"${DIRS}\") -# " COMPONENT Runtime) +OPTION(MultiMC_INSTALL_SHARED_LIBS "if set, Qt's shared libraries will be copied to the installation directory on install") + +IF (MultiMC_INSTALL_SHARED_LIBS) + # Image formats + INSTALL(DIRECTORY "${QT_PLUGINS_DIR}/imageformats" DESTINATION ${PLUGIN_DEST_DIR} COMPONENT Runtime) + + # Platform plugins + INSTALL(DIRECTORY "${QT_PLUGINS_DIR}/platforms" DESTINATION ${PLUGIN_DEST_DIR} COMPONENT Runtime) + + # qtconf + INSTALL(CODE " + FILE(WRITE \"\${CMAKE_INSTALL_PREFIX}/${QTCONF_DEST_DIR}/qt.conf\" \"\") + " COMPONENT Runtime) + + + # Dirs to look for dependencies. + SET(DIRS "${QT_LIBRARY_DIRS}") + + INSTALL(CODE " + file(GLOB_RECURSE QTPLUGINS + \"\${CMAKE_INSTALL_PREFIX}/${PLUGIN_DEST_DIR}/plugins/*${CMAKE_SHARED_LIBRARY_SUFFIX}\") + include(BundleUtilities) + fixup_bundle(\"${APPS}\" \"\${QTPLUGINS}\" \"${DIRS}\") + " COMPONENT Runtime) +ENDIF() ######## Package ######## @@ -472,3 +533,18 @@ ENDIF() INCLUDE(CPack) include_directories(${PROJECT_BINARY_DIR}/include) + +### translation stuff + +file (GLOB TRANSLATIONS_FILES translations/*.ts) + +option (UPDATE_TRANSLATIONS "Update source translation translations/*.ts files (WARNING: make clean will delete the source .ts files! Danger!)") +if (UPDATE_TRANSLATIONS) + qt5_create_translation(QM_FILES ${FILES_TO_TRANSLATE} ${TRANSLATIONS_FILES}) +else (UPDATE_TRANSLATIONS) + qt5_add_translation(QM_FILES ${TRANSLATIONS_FILES}) +endif (UPDATE_TRANSLATIONS) + +add_custom_target (translations DEPENDS ${QM_FILES}) + +install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/translations) diff --git a/MultiMC.cpp b/MultiMC.cpp index 88eb3e62..25c53185 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -3,36 +3,49 @@ #include <iostream> #include <QDir> #include <QNetworkAccessManager> +#include <QTranslator> +#include <QLibraryInfo> +#include <QMessageBox> #include "gui/mainwindow.h" +#include "gui/versionselectdialog.h" #include "logic/lists/InstanceList.h" #include "logic/lists/IconList.h" +#include "logic/lists/LwjglVersionList.h" +#include "logic/lists/MinecraftVersionList.h" +#include "logic/lists/ForgeVersionList.h" + #include "logic/InstanceLauncher.h" +#include "logic/net/HttpMetaCache.h" +#include "logic/JavaUtils.h" #include "pathutils.h" #include "cmdutils.h" #include <inisettingsobject.h> #include <setting.h> +#include <logger/QsLog.h> +#include <logger/QsLogDest.h> #include "config.h" using namespace Util::Commandline; -MultiMC::MultiMC ( int& argc, char** argv ) - :QApplication ( argc, argv ) +MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) { - setOrganizationName("Forkk"); - setApplicationName("MultiMC 5"); - + setOrganizationName("MultiMC"); + setApplicationName("MultiMC5"); + + initTranslations(); + // Print app header std::cout << "MultiMC 5" << std::endl; std::cout << "(c) 2013 MultiMC Contributors" << std::endl << std::endl; - + // Commandline parsing QHash<QString, QVariant> args; { Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); - + // --help parser.addSwitch("help"); parser.addShortOpt("help", 'h'); @@ -44,33 +57,37 @@ MultiMC::MultiMC ( int& argc, char** argv ) // --dir parser.addOption("dir", applicationDirPath()); parser.addShortOpt("dir", 'd'); - parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of the binary location (use '.' for current)"); + parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of " + "the binary location (use '.' for current)"); // --update parser.addOption("update"); parser.addShortOpt("update", 'u'); - parser.addDocumentation("update", "replaces the given file with the running executable", "<path>"); + parser.addDocumentation("update", "replaces the given file with the running executable", + "<path>"); // --quietupdate parser.addSwitch("quietupdate"); parser.addShortOpt("quietupdate", 'U'); - parser.addDocumentation("quietupdate", "doesn't restart MultiMC after installing updates"); + parser.addDocumentation("quietupdate", + "doesn't restart MultiMC after installing updates"); // --launch parser.addOption("launch"); parser.addShortOpt("launch", 'l'); parser.addDocumentation("launch", "tries to launch the given instance", "<inst>"); - + // parse the arguments try { args = parser.parse(arguments()); } - catch(ParsingError e) + catch (ParsingError e) { std::cerr << "CommandLineError: " << e.what() << std::endl; - std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." << std::endl; + std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." + << std::endl; m_status = MultiMC::Failed; return; } - + // display help and exit if (args["help"].toBool()) { @@ -78,27 +95,29 @@ MultiMC::MultiMC ( int& argc, char** argv ) m_status = MultiMC::Succeeded; return; } - + // display version and exit if (args["version"].toBool()) { std::cout << "Version " << VERSION_STR << std::endl; std::cout << "Git " << GIT_COMMIT << std::endl; - std::cout << "Tag: " << JENKINS_BUILD_TAG << " " << (ARCH==x64?"x86_64":"x86") << std::endl; + std::cout << "Tag: " << JENKINS_BUILD_TAG << " " << (ARCH == x64 ? "x86_64" : "x86") + << std::endl; m_status = MultiMC::Succeeded; return; } - + // update // Note: cwd is always the current executable path! if (!args["update"].isNull()) { - std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString()) << std::endl; + std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString()) + << std::endl; QString cwd = QDir::currentPath(); QDir::setCurrent(applicationDirPath()); QFile file(applicationFilePath()); file.copy(args["update"].toString()); - if(args["quietupdate"].toBool()) + if (args["quietupdate"].toBool()) { m_status = MultiMC::Succeeded; return; @@ -106,26 +125,34 @@ MultiMC::MultiMC ( int& argc, char** argv ) QDir::setCurrent(cwd); } } - + // change directory QDir::setCurrent(args["dir"].toString()); - + + // init the logger + initLogger(); + // load settings initGlobalSettings(); + // and instances - m_instances = new InstanceList(m_settings->get("InstanceDir").toString(),this); - std::cout << "Loading Instances..." << std::endl; + m_instances.reset(new InstanceList(m_settings->get("InstanceDir").toString(), this)); + QLOG_INFO() << "Loading Instances..."; m_instances->loadList(); - // network manager - m_qnam = new QNetworkAccessManager(this); - + + // init the http meta cache + initHttpMetaCache(); + + // create the global network manager + m_qnam.reset(new QNetworkAccessManager(this)); + // Register meta types. qRegisterMetaType<LoginResponse>("LoginResponse"); - + // launch instance, if that's what should be done if (!args["launch"].isNull()) { - if(InstanceLauncher(args["launch"].toString()).launch()) + if (InstanceLauncher(args["launch"].toString()).launch()) m_status = MultiMC::Succeeded; else m_status = MultiMC::Failed; @@ -136,87 +163,206 @@ MultiMC::MultiMC ( int& argc, char** argv ) MultiMC::~MultiMC() { - delete m_settings; + if (m_mmc_translator) + { + removeTranslator(m_mmc_translator.get()); + } + if (m_qt_translator) + { + removeTranslator(m_qt_translator.get()); + } +} + +void MultiMC::initTranslations() +{ + m_qt_translator.reset(new QTranslator()); + if (m_qt_translator->load("qt_" + QLocale::system().name(), + QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + { + std::cout << "Loading Qt Language File for " + << QLocale::system().name().toLocal8Bit().constData() << "..."; + if (!installTranslator(m_qt_translator.get())) + { + std::cout << " failed."; + m_qt_translator.reset(); + } + std::cout << std::endl; + } + else + { + m_qt_translator.reset(); + } + + m_mmc_translator.reset(new QTranslator()); + if (m_mmc_translator->load("mmc_" + QLocale::system().name(), + QDir("translations").absolutePath())) + { + std::cout << "Loading MMC Language File for " + << QLocale::system().name().toLocal8Bit().constData() << "..."; + if (!installTranslator(m_mmc_translator.get())) + { + std::cout << " failed."; + m_mmc_translator.reset(); + } + std::cout << std::endl; + } + else + { + m_mmc_translator.reset(); + } +} + +void MultiMC::initLogger() +{ + // init the logging mechanism + QsLogging::Logger &logger = QsLogging::Logger::instance(); + logger.setLoggingLevel(QsLogging::TraceLevel); + m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination("MultiMC.log"); + m_debugDestination = QsLogging::DestinationFactory::MakeDebugOutputDestination(); + logger.addDestination(m_fileDestination.get()); + logger.addDestination(m_debugDestination.get()); + // log all the things + logger.setLoggingLevel(QsLogging::TraceLevel); } void MultiMC::initGlobalSettings() { - m_settings = new INISettingsObject(applicationDirPath() + "/multimc.cfg", this); - // Updates + m_settings.reset(new INISettingsObject("multimc.cfg", this)); + // Updates m_settings->registerSetting(new Setting("UseDevBuilds", false)); m_settings->registerSetting(new Setting("AutoUpdate", true)); - + // Folders m_settings->registerSetting(new Setting("InstanceDir", "instances")); m_settings->registerSetting(new Setting("CentralModsDir", "mods")); m_settings->registerSetting(new Setting("LWJGLDir", "lwjgl")); - + // Console m_settings->registerSetting(new Setting("ShowConsole", true)); m_settings->registerSetting(new Setting("AutoCloseConsole", true)); - + // Toolbar settings m_settings->registerSetting(new Setting("InstanceToolbarVisible", true)); m_settings->registerSetting(new Setting("InstanceToolbarPosition", QPoint())); - + // Console Colors -// m_settings->registerSetting(new Setting("SysMessageColor", QColor(Qt::blue))); -// m_settings->registerSetting(new Setting("StdOutColor", QColor(Qt::black))); -// m_settings->registerSetting(new Setting("StdErrColor", QColor(Qt::red))); - + // m_settings->registerSetting(new Setting("SysMessageColor", QColor(Qt::blue))); + // m_settings->registerSetting(new Setting("StdOutColor", QColor(Qt::black))); + // m_settings->registerSetting(new Setting("StdErrColor", QColor(Qt::red))); + // Window Size m_settings->registerSetting(new Setting("LaunchMaximized", false)); m_settings->registerSetting(new Setting("MinecraftWinWidth", 854)); m_settings->registerSetting(new Setting("MinecraftWinHeight", 480)); - + // Auto login m_settings->registerSetting(new Setting("AutoLogin", false)); - + // Memory m_settings->registerSetting(new Setting("MinMemAlloc", 512)); m_settings->registerSetting(new Setting("MaxMemAlloc", 1024)); m_settings->registerSetting(new Setting("PermGen", 64)); - + // Java Settings - m_settings->registerSetting(new Setting("JavaPath", "java")); + m_settings->registerSetting(new Setting("JavaPath", "")); + m_settings->registerSetting(new Setting("LastHostname", "")); m_settings->registerSetting(new Setting("JvmArgs", "")); - + // Custom Commands m_settings->registerSetting(new Setting("PreLaunchCommand", "")); m_settings->registerSetting(new Setting("PostExitCommand", "")); - + // The cat m_settings->registerSetting(new Setting("TheCat", false)); + + // Shall the main window hide on instance launch + m_settings->registerSetting(new Setting("NoHide", false)); + + // Persistent value for the client ID + m_settings->registerSetting(new Setting("YggdrasilClientToken", "")); + QString currentYggID = m_settings->get("YggdrasilClientToken").toString(); + if (currentYggID.isEmpty()) + { + QUuid uuid = QUuid::createUuid(); + m_settings->set("YggdrasilClientToken", uuid.toString()); + } } -IconList* MultiMC::icons() +void MultiMC::initHttpMetaCache() { - if ( !m_icons ) + m_metacache.reset(new HttpMetaCache("metacache")); + m_metacache->addBase("assets", QDir("assets").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("skins", QDir("accounts/skins").absolutePath()); + m_metacache->Load(); +} + +std::shared_ptr<IconList> MultiMC::icons() +{ + if (!m_icons) { - m_icons = new IconList; + m_icons.reset(new IconList); } return m_icons; } +std::shared_ptr<LWJGLVersionList> MultiMC::lwjgllist() +{ + if (!m_lwjgllist) + { + m_lwjgllist.reset(new LWJGLVersionList()); + } + return m_lwjgllist; +} + +std::shared_ptr<ForgeVersionList> MultiMC::forgelist() +{ + if (!m_forgelist) + { + m_forgelist.reset(new ForgeVersionList()); + } + return m_forgelist; +} + +std::shared_ptr<MinecraftVersionList> MultiMC::minecraftlist() +{ + if (!m_minecraftlist) + { + m_minecraftlist.reset(new MinecraftVersionList()); + } + return m_minecraftlist; +} + +std::shared_ptr<JavaVersionList> MultiMC::javalist() +{ + if (!m_javalist) + { + m_javalist.reset(new JavaVersionList()); + } + return m_javalist; +} int main(int argc, char *argv[]) { // initialize Qt MultiMC app(argc, argv); - + // show main window MainWindow mainWin; mainWin.show(); - - switch(app.status()) + mainWin.checkSetDefaultJava(); + + switch (app.status()) { - case MultiMC::Initialized: - return app.exec(); - case MultiMC::Failed: - return 1; - case MultiMC::Succeeded: - return 0; + case MultiMC::Initialized: + return app.exec(); + case MultiMC::Failed: + return 1; + case MultiMC::Succeeded: + return 0; } } -#include "MultiMC.moc"
\ No newline at end of file +#include "MultiMC.moc" @@ -3,11 +3,20 @@ #include <QApplication> #include "MultiMCVersion.h" #include "config.h" +#include <memory> +#include "logger/QsLog.h" +#include "logger/QsLogDest.h" + +class MinecraftVersionList; +class LWJGLVersionList; +class HttpMetaCache; class SettingsObject; class InstanceList; class IconList; class QNetworkAccessManager; +class ForgeVersionList; +class JavaVersionList; #if defined(MMC) #undef MMC @@ -24,45 +33,75 @@ public: Succeeded, Initialized, }; - + public: - MultiMC ( int& argc, char** argv ); + MultiMC(int &argc, char **argv); virtual ~MultiMC(); - - SettingsObject * settings() + + std::shared_ptr<SettingsObject> settings() { return m_settings; - }; - - InstanceList * instances() + } + + std::shared_ptr<InstanceList> instances() { return m_instances; - }; - - IconList * icons(); - + } + + std::shared_ptr<IconList> icons(); + Status status() { return m_status; } - + MultiMCVersion version() { return m_version; } - - QNetworkAccessManager * qnam() + + std::shared_ptr<QNetworkAccessManager> qnam() { return m_qnam; } + + std::shared_ptr<HttpMetaCache> metacache() + { + return m_metacache; + } + + std::shared_ptr<LWJGLVersionList> lwjgllist(); + + std::shared_ptr<ForgeVersionList> forgelist(); + + std::shared_ptr<MinecraftVersionList> minecraftlist(); + + std::shared_ptr<JavaVersionList> javalist(); + private: + void initLogger(); + void initGlobalSettings(); - + + void initHttpMetaCache(); + + void initTranslations(); + private: - SettingsObject * m_settings = nullptr; - InstanceList * m_instances = nullptr; - IconList * m_icons = nullptr; - QNetworkAccessManager * m_qnam = nullptr; + std::shared_ptr<QTranslator> m_qt_translator; + std::shared_ptr<QTranslator> m_mmc_translator; + std::shared_ptr<SettingsObject> m_settings; + std::shared_ptr<InstanceList> m_instances; + std::shared_ptr<IconList> m_icons; + std::shared_ptr<QNetworkAccessManager> m_qnam; + std::shared_ptr<HttpMetaCache> m_metacache; + std::shared_ptr<LWJGLVersionList> m_lwjgllist; + std::shared_ptr<ForgeVersionList> m_forgelist; + std::shared_ptr<MinecraftVersionList> m_minecraftlist; + std::shared_ptr<JavaVersionList> m_javalist; + QsLogging::DestinationPtr m_fileDestination; + QsLogging::DestinationPtr m_debugDestination; + Status m_status = MultiMC::Failed; MultiMCVersion m_version = {VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD}; -};
\ No newline at end of file +}; @@ -1,8 +1,19 @@ ![MultiMC](http://i.imgur.com/QJXbz.png) -<h1>MultiMC 5</h1> -<p>MultiMC is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. It also allows you to easily install and remove mods by simply dragging and dropping.</p> -<h3>License</h3> -<p>Copyright © 2013 MultiMC Contributors</p> -<p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at <a href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>.</p> -<p>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.</p> +MultiMC 5 +========= + +MultiMC is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. It also allows you to easily install and remove mods by simply dragging and dropping. + +## Building +Check [BUILD.md](BUILD.md) for build instructions. + +## Contributing +The repository is currently managed by @peterix and @drayshak - we're the ones likely to review pull requests. If you'd like to contribute to the project please talk to us on IRC (Esper/#MultiMC) first! This helps us organise ideas and keep in contact with you, and we're unlikely to accept anything blindly. + +## License +Copyright © 2013 MultiMC Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](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. diff --git a/depends/pack200/CMakeLists.txt b/depends/pack200/CMakeLists.txt new file mode 100644 index 00000000..386c8bb8 --- /dev/null +++ b/depends/pack200/CMakeLists.txt @@ -0,0 +1,59 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +IF(WIN32) + # In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows + cmake_policy(SET CMP0020 OLD) +ENDIF() + +project(unpack200) + +# Find ZLIB for quazip +# Use system zlib on unix and Qt ZLIB on Windows +IF(UNIX) + find_package(ZLIB REQUIRED) +ELSE(UNIX) + get_filename_component (ZLIB_FOUND_DIR "${Qt5Core_DIR}/../../../include/QtZlib" ABSOLUTE) + SET(ZLIB_INCLUDE_DIRS ${ZLIB_FOUND_DIR} CACHE PATH "Path to ZLIB headers of Qt") + SET(ZLIB_LIBRARIES "") + IF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h") + MESSAGE("Please specify a valid zlib include dir") + ENDIF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h") +ENDIF(UNIX) + +SET(PACK200_SRC +include/unpack200.h +src/bands.cpp +src/bands.h +src/bytes.cpp +src/bytes.h +src/coding.cpp +src/coding.h +src/constants.h +src/defines.h +src/unpack200.cpp +src/unpack.cpp +src/unpack.h +src/utils.cpp +src/utils.h +src/zip.cpp +src/zip.h +) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +SET(PACK200_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) +include_directories( + include + ${ZLIB_INCLUDE_DIRS} +) +add_library(unpack200 STATIC ${PACK200_SRC}) + +IF(UNIX) + target_link_libraries(unpack200 ${ZLIB_LIBRARIES}) +ELSE() + # zlib is part of Qt on windows. use it. + QT5_USE_MODULES(unpack200 Core) +ENDIF() + +add_executable(anti200 anti200.cpp) +target_link_libraries(anti200 unpack200) diff --git a/depends/pack200/LICENSE b/depends/pack200/LICENSE new file mode 100644 index 00000000..b40a0f45 --- /dev/null +++ b/depends/pack200/LICENSE @@ -0,0 +1,347 @@ +The GNU General Public License (GPL) + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for this service if you wish), +that you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs; and that you know you +can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must +make sure that they, too, receive or can get the source code. And you must +show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program proprietary. +To prevent this, we have made it clear that any patent must be licensed for +everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is included +without limitation in the term "modification".) Each licensee is addressed as +"you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as +you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may +at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus +forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all of +these conditions: + + a) You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or + in part contains or is derived from the Program or any part thereof, to be + licensed as a whole at no charge to all third parties under the terms of + this License. + + c) If the modified program normally reads commands interactively when run, + you must cause it, when started running for such interactive use in the + most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may redistribute + the program under these conditions, and telling the user how to view a copy + of this License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the Program is + not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, and +its terms, do not apply to those sections when you distribute them as separate +works. But when you distribute the same sections as part of a whole which is a +work based on the Program, the distribution of the whole must be on the terms +of this License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based on +the Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and +2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 above + on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only + for noncommercial distribution and only if you received the program in + object code or executable form with such an offer, in accord with + Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code +distributed need not include anything that is normally distributed (in either +source or binary form) with the major components (compiler, kernel, and so on) +of the operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source +code from the same place counts as distribution of the source code, even though +third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Program +or its derivative works. These actions are prohibited by law if you do not +accept this License. Therefore, by modifying or distributing the Program (or +any work based on the Program), you indicate your acceptance of this License to +do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor to +copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of the +rights granted herein. You are not responsible for enforcing compliance by +third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In +such case, this License incorporates the limitation as if written in the body +of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any later +version", you have the option of following the terms and conditions either of +that version or of any later version published by the Free Software Foundation. +If the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of +all derivatives of our free software and of promoting the sharing and reuse of +software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE +PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, +YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE +PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA +BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it +starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes + with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free + software, and you are welcome to redistribute it under certain conditions; + type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than 'show w' and 'show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + 'Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General Public +License instead of this License. + + +"CLASSPATH" EXCEPTION TO THE GPL + +Certain source files distributed by Oracle America and/or its affiliates are +subject to the following clarification and special exception to the GPL, but +only where Oracle has expressly included in the particular source file's header +the words "Oracle designates this particular file as subject to the "Classpath" +exception as provided by Oracle in the LICENSE file that accompanied this code." + + Linking this library statically or dynamically with other modules is making + a combined work based on this library. Thus, the terms and conditions of + the GNU General Public License cover the whole combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent modules, + and to copy and distribute the resulting executable under terms of your + choice, provided that you also meet, for each linked independent module, + the terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. If + you modify this library, you may extend this exception to your version of + the library, but you are not obligated to do so. If you do not wish to do + so, delete this exception statement from your version. diff --git a/depends/pack200/anti200.cpp b/depends/pack200/anti200.cpp new file mode 100644 index 00000000..3dfdb5dc --- /dev/null +++ b/depends/pack200/anti200.cpp @@ -0,0 +1,28 @@ +/* + * This is trivial. Do what thou wilt with it. Public domain. + */ + +#include <stdexcept> +#include <iostream> +#include "unpack200.h" + +int main(int argc, char **argv) +{ + if (argc == 3) + { + try + { + unpack_200(argv[1], argv[2]); + } + catch (std::runtime_error &e) + { + std::cerr << "Bad things happened: " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } + else + std::cerr << "Simple pack200 unpacker!" << std::endl << "Run like this:" << std::endl + << " " << argv[0] << " input.jar.lzma output.jar" << std::endl; + return EXIT_FAILURE; +} diff --git a/depends/pack200/include/unpack200.h b/depends/pack200/include/unpack200.h new file mode 100644 index 00000000..bcee8009 --- /dev/null +++ b/depends/pack200/include/unpack200.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#pragma once +#include <string> + +/** + * @brief Unpack a PACK200 file + * + * @param input_path Path to the input file in PACK200 format. System native string encoding. + * @param output_path Path to the output file in PACK200 format. System native string encoding. + * @return void + * @throw std::runtime_error for any error encountered + */ +void unpack_200(std::string input_path, std::string output_path); diff --git a/depends/pack200/src/bands.cpp b/depends/pack200/src/bands.cpp new file mode 100644 index 00000000..1c10b35b --- /dev/null +++ b/depends/pack200/src/bands.cpp @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2002, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -*- C++ -*- +// Small program for unpacking specially compressed Java packages. +// John R. Rose + +#include <sys/types.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <assert.h> +#include <stdint.h> + +#include "defines.h" +#include "bytes.h" +#include "utils.h" +#include "coding.h" +#include "bands.h" + +#include "constants.h" +#include "unpack.h" + +void band::readData(int expectedLength) +{ + assert(expectedLength >= 0); + assert(vs[0].cmk == cmk_ERROR); + if (expectedLength != 0) + { + assert(length == 0); + length = expectedLength; + } + if (length == 0) + { + assert((rplimit = cm.vs0.rp = u->rp) != nullptr); + return; + } + assert(length > 0); + + bool is_BYTE1 = (defc->spec == BYTE1_spec); + + if (is_BYTE1) + { + // No possibility of coding change. Sizing is exact. + u->ensure_input(length); + } + else + { + // Make a conservatively generous estimate of band size in bytes. + // Assume B == 5 everywhere. + // Assume awkward pop with all {U} values (2*5 per value) + int64_t generous = (int64_t)length * (B_MAX * 3 + 1) + C_SLOP; + u->ensure_input(generous); + } + + // Read one value to see what it might be. + int XB = _meta_default; + int cp1 = 0, cp2 = 0; + if (!is_BYTE1) + { + // must be a variable-length coding + assert(defc->B() > 1 && defc->L() > 0); + + value_stream xvs; + coding *valc = defc; + if (valc->D() != 0) + { + valc = coding::findBySpec(defc->B(), defc->H(), defc->S()); + assert(!valc->isMalloc); + } + xvs.init(u->rp, u->rplimit, valc); + int X = xvs.getInt(); + if (valc->S() != 0) + { + assert(valc->min <= -256); + XB = -1 - X; + } + else + { + int L = valc->L(); + assert(valc->max >= L + 255); + XB = X - L; + } + if (0 <= XB && XB < 256) + { + // Skip over the escape value. + u->rp = xvs.rp; + cp1 = 1; + } + else + { + // No, it's still default. + XB = _meta_default; + } + } + + if (XB <= _meta_canon_max) + { + byte XB_byte = (byte)XB; + byte *XB_ptr = &XB_byte; + cm.init(u->rp, u->rplimit, XB_ptr, 0, defc, length, nullptr); + } + else + { + assert(u->meta_rp != nullptr); + // Scribble the initial byte onto the band. + byte *save_meta_rp = --u->meta_rp; + byte save_meta_xb = (*save_meta_rp); + (*save_meta_rp) = (byte)XB; + cm.init(u->rp, u->rplimit, u->meta_rp, 0, defc, length, nullptr); + (*save_meta_rp) = save_meta_xb; // put it back, just to be tidy + } + rplimit = u->rp; + + rewind(); +} + +void band::setIndex(cpindex *ix_) +{ + assert(ix_ == nullptr || ixTag == ix_->ixTag); + ix = ix_; +} +void band::setIndexByTag(byte tag) +{ + setIndex(u->cp.getIndex(tag)); +} + +entry *band::getRefCommon(cpindex *ix_, bool nullOKwithCaller) +{ + assert(ix_->ixTag == ixTag || + (ixTag == CONSTANT_Literal && ix_->ixTag >= CONSTANT_Integer && + ix_->ixTag <= CONSTANT_String)); + int n = vs[0].getInt() - nullOK; + // Note: band-local nullOK means nullptr encodes as 0. + // But nullOKwithCaller means caller is willing to tolerate a nullptr. + entry *ref = ix_->get(n); + if (ref == nullptr && !(nullOKwithCaller && n == -1)) + unpack_abort(n == -1 ? "nullptr ref" : "bad ref"); + return ref; +} + +int64_t band::getLong(band &lo_band, bool have_hi) +{ + band &hi_band = (*this); + assert(lo_band.bn == hi_band.bn + 1); + uint32_t lo = lo_band.getInt(); + if (!have_hi) + { + assert(hi_band.length == 0); + return makeLong(0, lo); + } + uint32_t hi = hi_band.getInt(); + return makeLong(hi, lo); +} + +int band::getIntTotal() +{ + if (length == 0) + return 0; + if (total_memo > 0) + return total_memo - 1; + int total = getInt(); + // overflow checks require that none of the addends are <0, + // and that the partial sums never overflow (wrap negative) + if (total < 0) + { + unpack_abort("overflow detected"); + } + for (int k = length - 1; k > 0; k--) + { + int prev_total = total; + total += vs[0].getInt(); + if (total < prev_total) + { + unpack_abort("overflow detected"); + } + } + rewind(); + total_memo = total + 1; + return total; +} + +int band::getIntCount(int tag) +{ + if (length == 0) + return 0; + if (tag >= HIST0_MIN && tag <= HIST0_MAX) + { + if (hist0 == nullptr) + { + // Lazily calculate an approximate histogram. + hist0 = U_NEW(int, (HIST0_MAX - HIST0_MIN) + 1); + for (int k = length; k > 0; k--) + { + int x = vs[0].getInt(); + if (x >= HIST0_MIN && x <= HIST0_MAX) + hist0[x - HIST0_MIN] += 1; + } + rewind(); + } + return hist0[tag - HIST0_MIN]; + } + int total = 0; + for (int k = length; k > 0; k--) + { + total += (vs[0].getInt() == tag) ? 1 : 0; + } + rewind(); + return total; +} + +#define INDEX_INIT(tag, nullOK, subindex) ((tag) + (subindex) * SUBINDEX_BIT + (nullOK) * 256) + +#define INDEX(tag) INDEX_INIT(tag, 0, 0) +#define NULL_OR_INDEX(tag) INDEX_INIT(tag, 1, 0) +#define SUB_INDEX(tag) INDEX_INIT(tag, 0, 1) +#define NO_INDEX 0 + +struct band_init +{ + int defc; + int index; +}; + +#define BAND_INIT(name, cspec, ix) \ + { \ + cspec, ix \ + } + +const band_init all_band_inits[] = + { + // BAND_INIT(archive_magic, BYTE1_spec, 0), + // BAND_INIT(archive_header, UNSIGNED5_spec, 0), + // BAND_INIT(band_headers, BYTE1_spec, 0), + BAND_INIT(cp_Utf8_prefix, DELTA5_spec, 0), BAND_INIT(cp_Utf8_suffix, UNSIGNED5_spec, 0), + BAND_INIT(cp_Utf8_chars, CHAR3_spec, 0), BAND_INIT(cp_Utf8_big_suffix, DELTA5_spec, 0), + BAND_INIT(cp_Utf8_big_chars, DELTA5_spec, 0), BAND_INIT(cp_Int, UDELTA5_spec, 0), + BAND_INIT(cp_Float, UDELTA5_spec, 0), BAND_INIT(cp_Long_hi, UDELTA5_spec, 0), + BAND_INIT(cp_Long_lo, DELTA5_spec, 0), BAND_INIT(cp_Double_hi, UDELTA5_spec, 0), + BAND_INIT(cp_Double_lo, DELTA5_spec, 0), + BAND_INIT(cp_String, UDELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Class, UDELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Signature_form, DELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Signature_classes, UDELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Descr_name, DELTA5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(cp_Descr_type, UDELTA5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(cp_Field_class, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Field_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(cp_Method_class, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Method_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(cp_Imethod_class, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(cp_Imethod_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(attr_definition_headers, BYTE1_spec, 0), + BAND_INIT(attr_definition_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(attr_definition_layout, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(ic_this_class, UDELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(ic_flags, UNSIGNED5_spec, 0), + BAND_INIT(ic_outer_class, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(ic_name, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), + BAND_INIT(class_this, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_super, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_interface_count, DELTA5_spec, 0), + BAND_INIT(class_interface, DELTA5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_field_count, DELTA5_spec, 0), + BAND_INIT(class_method_count, DELTA5_spec, 0), + BAND_INIT(field_descr, DELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(field_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(field_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(field_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(field_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(field_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(field_ConstantValue_KQ, UNSIGNED5_spec, INDEX(CONSTANT_Literal)), + BAND_INIT(field_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(field_metadata_bands, -1, -1), BAND_INIT(field_attr_bands, -1, -1), + BAND_INIT(method_descr, MDELTA5_spec, INDEX(CONSTANT_NameandType)), + BAND_INIT(method_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(method_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(method_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(method_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(method_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(method_Exceptions_N, UNSIGNED5_spec, 0), + BAND_INIT(method_Exceptions_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(method_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(method_metadata_bands, -1, -1), BAND_INIT(method_attr_bands, -1, -1), + BAND_INIT(class_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(class_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(class_SourceFile_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), + BAND_INIT(class_EnclosingMethod_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_EnclosingMethod_RDN, UNSIGNED5_spec, + NULL_OR_INDEX(CONSTANT_NameandType)), + BAND_INIT(class_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(class_metadata_bands, -1, -1), + BAND_INIT(class_InnerClasses_N, UNSIGNED5_spec, 0), + BAND_INIT(class_InnerClasses_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(class_InnerClasses_F, UNSIGNED5_spec, 0), + BAND_INIT(class_InnerClasses_outer_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(class_InnerClasses_name_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)), + BAND_INIT(class_ClassFile_version_minor_H, UNSIGNED5_spec, 0), + BAND_INIT(class_ClassFile_version_major_H, UNSIGNED5_spec, 0), + BAND_INIT(class_attr_bands, -1, -1), BAND_INIT(code_headers, BYTE1_spec, 0), + BAND_INIT(code_max_stack, UNSIGNED5_spec, 0), + BAND_INIT(code_max_na_locals, UNSIGNED5_spec, 0), + BAND_INIT(code_handler_count, UNSIGNED5_spec, 0), + BAND_INIT(code_handler_start_P, BCI5_spec, 0), + BAND_INIT(code_handler_end_PO, BRANCH5_spec, 0), + BAND_INIT(code_handler_catch_PO, BRANCH5_spec, 0), + BAND_INIT(code_handler_class_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(code_flags_hi, UNSIGNED5_spec, 0), + BAND_INIT(code_flags_lo, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_count, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_indexes, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_calls, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_frame_T, BYTE1_spec, 0), + BAND_INIT(code_StackMapTable_local_N, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_stack_N, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_offset, UNSIGNED5_spec, 0), + BAND_INIT(code_StackMapTable_T, BYTE1_spec, 0), + BAND_INIT(code_StackMapTable_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)), + BAND_INIT(code_StackMapTable_P, BCI5_spec, 0), + BAND_INIT(code_LineNumberTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_LineNumberTable_bci_P, BCI5_spec, 0), + BAND_INIT(code_LineNumberTable_line, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTable_bci_P, BCI5_spec, 0), + BAND_INIT(code_LocalVariableTable_span_O, BRANCH5_spec, 0), + BAND_INIT(code_LocalVariableTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(code_LocalVariableTable_type_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)), + BAND_INIT(code_LocalVariableTable_slot, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_N, UNSIGNED5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_bci_P, BCI5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_span_O, BRANCH5_spec, 0), + BAND_INIT(code_LocalVariableTypeTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(code_LocalVariableTypeTable_type_RS, UNSIGNED5_spec, + INDEX(CONSTANT_Signature)), + BAND_INIT(code_LocalVariableTypeTable_slot, UNSIGNED5_spec, 0), + BAND_INIT(code_attr_bands, -1, -1), BAND_INIT(bc_codes, BYTE1_spec, 0), + BAND_INIT(bc_case_count, UNSIGNED5_spec, 0), BAND_INIT(bc_case_value, DELTA5_spec, 0), + BAND_INIT(bc_byte, BYTE1_spec, 0), BAND_INIT(bc_short, DELTA5_spec, 0), + BAND_INIT(bc_local, UNSIGNED5_spec, 0), BAND_INIT(bc_label, BRANCH5_spec, 0), + BAND_INIT(bc_intref, DELTA5_spec, INDEX(CONSTANT_Integer)), + BAND_INIT(bc_floatref, DELTA5_spec, INDEX(CONSTANT_Float)), + BAND_INIT(bc_longref, DELTA5_spec, INDEX(CONSTANT_Long)), + BAND_INIT(bc_doubleref, DELTA5_spec, INDEX(CONSTANT_Double)), + BAND_INIT(bc_stringref, DELTA5_spec, INDEX(CONSTANT_String)), + BAND_INIT(bc_classref, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)), + BAND_INIT(bc_fieldref, DELTA5_spec, INDEX(CONSTANT_Fieldref)), + BAND_INIT(bc_methodref, UNSIGNED5_spec, INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_imethodref, DELTA5_spec, INDEX(CONSTANT_InterfaceMethodref)), + BAND_INIT(bc_thisfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)), + BAND_INIT(bc_superfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)), + BAND_INIT(bc_thismethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_supermethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_initref, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)), + BAND_INIT(bc_escref, UNSIGNED5_spec, INDEX(CONSTANT_All)), + BAND_INIT(bc_escrefsize, UNSIGNED5_spec, 0), BAND_INIT(bc_escsize, UNSIGNED5_spec, 0), + BAND_INIT(bc_escbyte, BYTE1_spec, 0), + BAND_INIT(file_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)), + BAND_INIT(file_size_hi, UNSIGNED5_spec, 0), BAND_INIT(file_size_lo, UNSIGNED5_spec, 0), + BAND_INIT(file_modtime, DELTA5_spec, 0), BAND_INIT(file_options, UNSIGNED5_spec, 0), + // BAND_INIT(file_bits, BYTE1_spec, 0), + {0, 0}}; + +band *band::makeBands(unpacker *u) +{ + band *tmp_all_bands = U_NEW(band, BAND_LIMIT); + for (int i = 0; i < BAND_LIMIT; i++) + { + assert((byte *)&all_band_inits[i + 1] < + (byte *)all_band_inits + sizeof(all_band_inits)); + const band_init &bi = all_band_inits[i]; + band &b = tmp_all_bands[i]; + coding *defc = coding::findBySpec(bi.defc); + assert((defc == nullptr) == (bi.defc == -1)); // no garbage, please + assert(defc == nullptr || !defc->isMalloc); + b.init(u, i, defc); + if (bi.index > 0) + { + b.nullOK = ((bi.index >> 8) & 1); + b.ixTag = (bi.index & 0xFF); + } + } + return tmp_all_bands; +} + +void band::initIndexes(unpacker *u) +{ + band *tmp_all_bands = u->all_bands; + for (int i = 0; i < BAND_LIMIT; i++) + { + band *scan = &tmp_all_bands[i]; + uint32_t tag = scan->ixTag; // Cf. #define INDEX(tag) above + if (tag != 0 && tag != CONSTANT_Literal && (tag & SUBINDEX_BIT) == 0) + { + scan->setIndex(u->cp.getIndex(tag)); + } + } +} diff --git a/depends/pack200/src/bands.h b/depends/pack200/src/bands.h new file mode 100644 index 00000000..a56cd7d5 --- /dev/null +++ b/depends/pack200/src/bands.h @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2002, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -*- C++ -*- +struct entry; +struct cpindex; +struct unpacker; + +struct band +{ + int bn; // band_number of this band + coding *defc; // default coding method + cpindex *ix; // CP entry mapping, if CPRefBand + byte ixTag; // 0 or 1; nullptr is coded as (nullOK?0:-1) + byte nullOK; // 0 or 1; nullptr is coded as (nullOK?0:-1) + int length; // expected # values + unpacker *u; // back pointer + + value_stream vs[2]; // source of values + coding_method cm; // method used for initial state of vs[0] + byte *rplimit; // end of band (encoded, transmitted) + + int total_memo; // cached value of getIntTotal, or -1 + int *hist0; // approximate. histogram + enum + { + HIST0_MIN = 0, + HIST0_MAX = 255 + }; // catches the usual cases + + // properties for attribute layout elements: + byte le_kind; // EK_XXX + byte le_bci; // 0,EK_BCI,EK_BCD,EK_BCO + byte le_back; // ==EF_BACK + byte le_len; // 0,1,2,4 (size in classfile), or call addr + band **le_body; // body of repl, union, call (nullptr-terminated) +// Note: EK_CASE elements use hist0 to record union tags. +#define le_casetags hist0 + + band &nextBand() + { + return this[1]; + } + band &prevBand() + { + return this[-1]; + } + + void init(unpacker *u_, int bn_, coding *defc_) + { + u = u_; + cm.u = u_; + bn = bn_; + defc = defc_; + } + void init(unpacker *u_, int bn_, int defcSpec) + { + init(u_, bn_, coding::findBySpec(defcSpec)); + } + void initRef(int ixTag_ = 0, bool nullOK_ = false) + { + ixTag = ixTag_; + nullOK = nullOK_; + setIndexByTag(ixTag); + } + + void expectMoreLength(int l) + { + assert(length >= 0); // able to accept a length + assert((int)l >= 0); // no overflow + assert(rplimit == nullptr); // readData not yet called + length += l; + assert(length >= l); // no overflow + } + + void setIndex(cpindex *ix_); + void setIndexByTag(byte tag); + + // Parse the band and its meta-coding header. + void readData(int expectedLength = 0); + + // Reset the band for another pass (Cf. Java Band.resetForSecondPass.) + void rewind() + { + cm.reset(&vs[0]); + } + + byte *&curRP() + { + return vs[0].rp; + } + byte *minRP() + { + return cm.vs0.rp; + } + byte *maxRP() + { + return rplimit; + } + size_t size() + { + return maxRP() - minRP(); + } + + int getByte() + { + assert(ix == nullptr); + return vs[0].getByte(); + } + int getInt() + { + assert(ix == nullptr); + return vs[0].getInt(); + } + entry *getRefN() + { + assert(ix != nullptr); + return getRefCommon(ix, true); + } + entry *getRef() + { + assert(ix != nullptr); + return getRefCommon(ix, false); + } + entry *getRefUsing(cpindex *ix2) + { + assert(ix == nullptr); + return getRefCommon(ix2, true); + } + entry *getRefCommon(cpindex *ix, bool nullOK); + int64_t getLong(band &lo_band, bool have_hi); + + static int64_t makeLong(uint32_t hi, uint32_t lo) + { + return ((uint64_t)hi << 32) + (((uint64_t)lo << 32) >> 32); + } + + int getIntTotal(); + int getIntCount(int tag); + + static band *makeBands(unpacker *u); + static void initIndexes(unpacker *u); +}; + +extern band all_bands[]; + +#define BAND_LOCAL /* \ + band* band_temp = all_bands; \ + band* all_bands = band_temp */ + +// Band schema: +enum band_number +{ + // e_archive_magic, + // e_archive_header, + // e_band_headers, + + // constant pool contents + e_cp_Utf8_prefix, + e_cp_Utf8_suffix, + e_cp_Utf8_chars, + e_cp_Utf8_big_suffix, + e_cp_Utf8_big_chars, + e_cp_Int, + e_cp_Float, + e_cp_Long_hi, + e_cp_Long_lo, + e_cp_Double_hi, + e_cp_Double_lo, + e_cp_String, + e_cp_Class, + e_cp_Signature_form, + e_cp_Signature_classes, + e_cp_Descr_name, + e_cp_Descr_type, + e_cp_Field_class, + e_cp_Field_desc, + e_cp_Method_class, + e_cp_Method_desc, + e_cp_Imethod_class, + e_cp_Imethod_desc, + + // bands which define transmission of attributes + e_attr_definition_headers, + e_attr_definition_name, + e_attr_definition_layout, + + // band for hardwired InnerClasses attribute (shared across the package) + e_ic_this_class, + e_ic_flags, + // These bands contain data only where flags sets ACC_IC_LONG_FORM: + e_ic_outer_class, + e_ic_name, + + // bands for carrying class schema information: + e_class_this, + e_class_super, + e_class_interface_count, + e_class_interface, + + // bands for class members + e_class_field_count, + e_class_method_count, + e_field_descr, + e_field_flags_hi, + e_field_flags_lo, + e_field_attr_count, + e_field_attr_indexes, + e_field_attr_calls, + e_field_ConstantValue_KQ, + e_field_Signature_RS, + e_field_metadata_bands, + e_field_attr_bands, + e_method_descr, + e_method_flags_hi, + e_method_flags_lo, + e_method_attr_count, + e_method_attr_indexes, + e_method_attr_calls, + e_method_Exceptions_N, + e_method_Exceptions_RC, + e_method_Signature_RS, + e_method_metadata_bands, + e_method_attr_bands, + e_class_flags_hi, + e_class_flags_lo, + e_class_attr_count, + e_class_attr_indexes, + e_class_attr_calls, + e_class_SourceFile_RUN, + e_class_EnclosingMethod_RC, + e_class_EnclosingMethod_RDN, + e_class_Signature_RS, + e_class_metadata_bands, + e_class_InnerClasses_N, + e_class_InnerClasses_RC, + e_class_InnerClasses_F, + e_class_InnerClasses_outer_RCN, + e_class_InnerClasses_name_RUN, + e_class_ClassFile_version_minor_H, + e_class_ClassFile_version_major_H, + e_class_attr_bands, + e_code_headers, + e_code_max_stack, + e_code_max_na_locals, + e_code_handler_count, + e_code_handler_start_P, + e_code_handler_end_PO, + e_code_handler_catch_PO, + e_code_handler_class_RCN, + + // code attributes + e_code_flags_hi, + e_code_flags_lo, + e_code_attr_count, + e_code_attr_indexes, + e_code_attr_calls, + e_code_StackMapTable_N, + e_code_StackMapTable_frame_T, + e_code_StackMapTable_local_N, + e_code_StackMapTable_stack_N, + e_code_StackMapTable_offset, + e_code_StackMapTable_T, + e_code_StackMapTable_RC, + e_code_StackMapTable_P, + e_code_LineNumberTable_N, + e_code_LineNumberTable_bci_P, + e_code_LineNumberTable_line, + e_code_LocalVariableTable_N, + e_code_LocalVariableTable_bci_P, + e_code_LocalVariableTable_span_O, + e_code_LocalVariableTable_name_RU, + e_code_LocalVariableTable_type_RS, + e_code_LocalVariableTable_slot, + e_code_LocalVariableTypeTable_N, + e_code_LocalVariableTypeTable_bci_P, + e_code_LocalVariableTypeTable_span_O, + e_code_LocalVariableTypeTable_name_RU, + e_code_LocalVariableTypeTable_type_RS, + e_code_LocalVariableTypeTable_slot, + e_code_attr_bands, + + // bands for bytecodes + e_bc_codes, + // remaining bands provide typed opcode fields required by the bc_codes + e_bc_case_count, + e_bc_case_value, + e_bc_byte, + e_bc_short, + e_bc_local, + e_bc_label, + + // ldc* operands: + e_bc_intref, + e_bc_floatref, + e_bc_longref, + e_bc_doubleref, + e_bc_stringref, + e_bc_classref, + e_bc_fieldref, + e_bc_methodref, + e_bc_imethodref, + + // _self_linker_op family + e_bc_thisfield, + e_bc_superfield, + e_bc_thismethod, + e_bc_supermethod, + + // bc_invokeinit family: + e_bc_initref, + + // bytecode escape sequences + e_bc_escref, + e_bc_escrefsize, + e_bc_escsize, + e_bc_escbyte, + + // file attributes and contents + e_file_name, + e_file_size_hi, + e_file_size_lo, + e_file_modtime, + e_file_options, + // e_file_bits, // handled specially as an appendix + BAND_LIMIT +}; + +// Symbolic names for bands, as if in a giant global struct: +//#define archive_magic all_bands[e_archive_magic] +//#define archive_header all_bands[e_archive_header] +//#define band_headers all_bands[e_band_headers] +#define cp_Utf8_prefix all_bands[e_cp_Utf8_prefix] +#define cp_Utf8_suffix all_bands[e_cp_Utf8_suffix] +#define cp_Utf8_chars all_bands[e_cp_Utf8_chars] +#define cp_Utf8_big_suffix all_bands[e_cp_Utf8_big_suffix] +#define cp_Utf8_big_chars all_bands[e_cp_Utf8_big_chars] +#define cp_Int all_bands[e_cp_Int] +#define cp_Float all_bands[e_cp_Float] +#define cp_Long_hi all_bands[e_cp_Long_hi] +#define cp_Long_lo all_bands[e_cp_Long_lo] +#define cp_Double_hi all_bands[e_cp_Double_hi] +#define cp_Double_lo all_bands[e_cp_Double_lo] +#define cp_String all_bands[e_cp_String] +#define cp_Class all_bands[e_cp_Class] +#define cp_Signature_form all_bands[e_cp_Signature_form] +#define cp_Signature_classes all_bands[e_cp_Signature_classes] +#define cp_Descr_name all_bands[e_cp_Descr_name] +#define cp_Descr_type all_bands[e_cp_Descr_type] +#define cp_Field_class all_bands[e_cp_Field_class] +#define cp_Field_desc all_bands[e_cp_Field_desc] +#define cp_Method_class all_bands[e_cp_Method_class] +#define cp_Method_desc all_bands[e_cp_Method_desc] +#define cp_Imethod_class all_bands[e_cp_Imethod_class] +#define cp_Imethod_desc all_bands[e_cp_Imethod_desc] +#define attr_definition_headers all_bands[e_attr_definition_headers] +#define attr_definition_name all_bands[e_attr_definition_name] +#define attr_definition_layout all_bands[e_attr_definition_layout] +#define ic_this_class all_bands[e_ic_this_class] +#define ic_flags all_bands[e_ic_flags] +#define ic_outer_class all_bands[e_ic_outer_class] +#define ic_name all_bands[e_ic_name] +#define class_this all_bands[e_class_this] +#define class_super all_bands[e_class_super] +#define class_interface_count all_bands[e_class_interface_count] +#define class_interface all_bands[e_class_interface] +#define class_field_count all_bands[e_class_field_count] +#define class_method_count all_bands[e_class_method_count] +#define field_descr all_bands[e_field_descr] +#define field_flags_hi all_bands[e_field_flags_hi] +#define field_flags_lo all_bands[e_field_flags_lo] +#define field_attr_count all_bands[e_field_attr_count] +#define field_attr_indexes all_bands[e_field_attr_indexes] +#define field_ConstantValue_KQ all_bands[e_field_ConstantValue_KQ] +#define field_Signature_RS all_bands[e_field_Signature_RS] +#define field_attr_bands all_bands[e_field_attr_bands] +#define method_descr all_bands[e_method_descr] +#define method_flags_hi all_bands[e_method_flags_hi] +#define method_flags_lo all_bands[e_method_flags_lo] +#define method_attr_count all_bands[e_method_attr_count] +#define method_attr_indexes all_bands[e_method_attr_indexes] +#define method_Exceptions_N all_bands[e_method_Exceptions_N] +#define method_Exceptions_RC all_bands[e_method_Exceptions_RC] +#define method_Signature_RS all_bands[e_method_Signature_RS] +#define method_attr_bands all_bands[e_method_attr_bands] +#define class_flags_hi all_bands[e_class_flags_hi] +#define class_flags_lo all_bands[e_class_flags_lo] +#define class_attr_count all_bands[e_class_attr_count] +#define class_attr_indexes all_bands[e_class_attr_indexes] +#define class_SourceFile_RUN all_bands[e_class_SourceFile_RUN] +#define class_EnclosingMethod_RC all_bands[e_class_EnclosingMethod_RC] +#define class_EnclosingMethod_RDN all_bands[e_class_EnclosingMethod_RDN] +#define class_Signature_RS all_bands[e_class_Signature_RS] +#define class_InnerClasses_N all_bands[e_class_InnerClasses_N] +#define class_InnerClasses_RC all_bands[e_class_InnerClasses_RC] +#define class_InnerClasses_F all_bands[e_class_InnerClasses_F] +#define class_InnerClasses_outer_RCN all_bands[e_class_InnerClasses_outer_RCN] +#define class_InnerClasses_name_RUN all_bands[e_class_InnerClasses_name_RUN] +#define class_ClassFile_version_minor_H all_bands[e_class_ClassFile_version_minor_H] +#define class_ClassFile_version_major_H all_bands[e_class_ClassFile_version_major_H] +#define class_attr_bands all_bands[e_class_attr_bands] +#define code_headers all_bands[e_code_headers] +#define code_max_stack all_bands[e_code_max_stack] +#define code_max_na_locals all_bands[e_code_max_na_locals] +#define code_handler_count all_bands[e_code_handler_count] +#define code_handler_start_P all_bands[e_code_handler_start_P] +#define code_handler_end_PO all_bands[e_code_handler_end_PO] +#define code_handler_catch_PO all_bands[e_code_handler_catch_PO] +#define code_handler_class_RCN all_bands[e_code_handler_class_RCN] +#define code_flags_hi all_bands[e_code_flags_hi] +#define code_flags_lo all_bands[e_code_flags_lo] +#define code_attr_count all_bands[e_code_attr_count] +#define code_attr_indexes all_bands[e_code_attr_indexes] +#define code_StackMapTable_N all_bands[e_code_StackMapTable_N] +#define code_StackMapTable_frame_T all_bands[e_code_StackMapTable_frame_T] +#define code_StackMapTable_local_N all_bands[e_code_StackMapTable_local_N] +#define code_StackMapTable_stack_N all_bands[e_code_StackMapTable_stack_N] +#define code_StackMapTable_offset all_bands[e_code_StackMapTable_offset] +#define code_StackMapTable_T all_bands[e_code_StackMapTable_T] +#define code_StackMapTable_RC all_bands[e_code_StackMapTable_RC] +#define code_StackMapTable_P all_bands[e_code_StackMapTable_P] +#define code_LineNumberTable_N all_bands[e_code_LineNumberTable_N] +#define code_LineNumberTable_bci_P all_bands[e_code_LineNumberTable_bci_P] +#define code_LineNumberTable_line all_bands[e_code_LineNumberTable_line] +#define code_LocalVariableTable_N all_bands[e_code_LocalVariableTable_N] +#define code_LocalVariableTable_bci_P all_bands[e_code_LocalVariableTable_bci_P] +#define code_LocalVariableTable_span_O all_bands[e_code_LocalVariableTable_span_O] +#define code_LocalVariableTable_name_RU all_bands[e_code_LocalVariableTable_name_RU] +#define code_LocalVariableTable_type_RS all_bands[e_code_LocalVariableTable_type_RS] +#define code_LocalVariableTable_slot all_bands[e_code_LocalVariableTable_slot] +#define code_LocalVariableTypeTable_N all_bands[e_code_LocalVariableTypeTable_N] +#define code_LocalVariableTypeTable_bci_P all_bands[e_code_LocalVariableTypeTable_bci_P] +#define code_LocalVariableTypeTable_span_O all_bands[e_code_LocalVariableTypeTable_span_O] +#define code_LocalVariableTypeTable_name_RU all_bands[e_code_LocalVariableTypeTable_name_RU] +#define code_LocalVariableTypeTable_type_RS all_bands[e_code_LocalVariableTypeTable_type_RS] +#define code_LocalVariableTypeTable_slot all_bands[e_code_LocalVariableTypeTable_slot] +#define code_attr_bands all_bands[e_code_attr_bands] +#define bc_codes all_bands[e_bc_codes] +#define bc_case_count all_bands[e_bc_case_count] +#define bc_case_value all_bands[e_bc_case_value] +#define bc_byte all_bands[e_bc_byte] +#define bc_short all_bands[e_bc_short] +#define bc_local all_bands[e_bc_local] +#define bc_label all_bands[e_bc_label] +#define bc_intref all_bands[e_bc_intref] +#define bc_floatref all_bands[e_bc_floatref] +#define bc_longref all_bands[e_bc_longref] +#define bc_doubleref all_bands[e_bc_doubleref] +#define bc_stringref all_bands[e_bc_stringref] +#define bc_classref all_bands[e_bc_classref] +#define bc_fieldref all_bands[e_bc_fieldref] +#define bc_methodref all_bands[e_bc_methodref] +#define bc_imethodref all_bands[e_bc_imethodref] +#define bc_thisfield all_bands[e_bc_thisfield] +#define bc_superfield all_bands[e_bc_superfield] +#define bc_thismethod all_bands[e_bc_thismethod] +#define bc_supermethod all_bands[e_bc_supermethod] +#define bc_initref all_bands[e_bc_initref] +#define bc_escref all_bands[e_bc_escref] +#define bc_escrefsize all_bands[e_bc_escrefsize] +#define bc_escsize all_bands[e_bc_escsize] +#define bc_escbyte all_bands[e_bc_escbyte] +#define file_name all_bands[e_file_name] +#define file_size_hi all_bands[e_file_size_hi] +#define file_size_lo all_bands[e_file_size_lo] +#define file_modtime all_bands[e_file_modtime] +#define file_options all_bands[e_file_options] diff --git a/depends/pack200/src/bytes.cpp b/depends/pack200/src/bytes.cpp new file mode 100644 index 00000000..d3808afa --- /dev/null +++ b/depends/pack200/src/bytes.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdint.h> +#include "defines.h" +#include "bytes.h" +#include "utils.h" + +static byte dummy[1 << 10]; + +bool bytes::inBounds(const void *p) +{ + return p >= ptr && p < limit(); +} + +void bytes::malloc(size_t len_) +{ + len = len_; + ptr = NEW(byte, add_size(len_, 1)); // add trailing zero byte always + if (ptr == nullptr) + { + // set ptr to some victim memory, to ease escape + set(dummy, sizeof(dummy) - 1); + unpack_abort(ERROR_ENOMEM); + } +} + +void bytes::realloc(size_t len_) +{ + if (len == len_) + return; // nothing to do + if (ptr == dummy) + return; // escaping from an error + if (ptr == nullptr) + { + malloc(len_); + return; + } + byte *oldptr = ptr; + ptr = (len_ >= PSIZE_MAX) ? nullptr : (byte *)::realloc(ptr, add_size(len_, 1)); + if (ptr != nullptr) + { + if (len < len_) + memset(ptr + len, 0, len_ - len); + ptr[len_] = 0; + len = len_; + } + else + { + ptr = oldptr; // ease our escape + unpack_abort(ERROR_ENOMEM); + } +} + +void bytes::free() +{ + if (ptr == dummy) + return; // escaping from an error + if (ptr != nullptr) + { + ::free(ptr); + } + len = 0; + ptr = 0; +} + +int bytes::indexOf(byte c) +{ + byte *p = (byte *)memchr(ptr, c, len); + return (p == 0) ? -1 : (int)(p - ptr); +} + +byte *bytes::writeTo(byte *bp) +{ + memcpy(bp, ptr, len); + return bp + len; +} + +int bytes::compareTo(bytes &other) +{ + size_t l1 = len; + size_t l2 = other.len; + int cmp = memcmp(ptr, other.ptr, (l1 < l2) ? l1 : l2); + if (cmp != 0) + return cmp; + return (l1 < l2) ? -1 : (l1 > l2) ? 1 : 0; +} + +void bytes::saveFrom(const void *ptr_, size_t len_) +{ + malloc(len_); + // Save as much as possible. + if (len_ > len) + { + assert(ptr == dummy); // error recovery + len_ = len; + } + copyFrom(ptr_, len_); +} + +//#TODO: Need to fix for exception handling +void bytes::copyFrom(const void *ptr_, size_t len_, size_t offset) +{ + assert(len_ == 0 || inBounds(ptr + offset)); + assert(len_ == 0 || inBounds(ptr + offset + len_ - 1)); + memcpy(ptr + offset, ptr_, len_); +} + +// Make sure there are 'o' bytes beyond the fill pointer, +// advance the fill pointer, and return the old fill pointer. +byte *fillbytes::grow(size_t s) +{ + size_t nlen = add_size(b.len, s); + if (nlen <= allocated) + { + b.len = nlen; + return limit() - s; + } + size_t maxlen = nlen; + if (maxlen < 128) + maxlen = 128; + if (maxlen < allocated * 2) + maxlen = allocated * 2; + if (allocated == 0) + { + // Initial buffer was not malloced. Do not reallocate it. + bytes old = b; + b.malloc(maxlen); + if (b.len == maxlen) + old.writeTo(b.ptr); + } + else + { + b.realloc(maxlen); + } + allocated = b.len; + if (allocated != maxlen) + { + b.len = nlen - s; // back up + return dummy; // scribble during error recov. + } + // after realloc, recompute pointers + b.len = nlen; + assert(b.len <= allocated); + return limit() - s; +} + +void fillbytes::ensureSize(size_t s) +{ + if (allocated >= s) + return; + size_t len0 = b.len; + grow(s - size()); + b.len = len0; // put it back +} + +int ptrlist::indexOf(const void *x) +{ + int len = length(); + for (int i = 0; i < len; i++) + { + if (get(i) == x) + return i; + } + return -1; +} + +void ptrlist::freeAll() +{ + int len = length(); + for (int i = 0; i < len; i++) + { + void *p = (void *)get(i); + if (p != nullptr) + { + ::free(p); + } + } + free(); +} + +int intlist::indexOf(int x) +{ + int len = length(); + for (int i = 0; i < len; i++) + { + if (get(i) == x) + return i; + } + return -1; +} diff --git a/depends/pack200/src/bytes.h b/depends/pack200/src/bytes.h new file mode 100644 index 00000000..2e4a9daf --- /dev/null +++ b/depends/pack200/src/bytes.h @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +struct bytes +{ + int8_t *ptr; + size_t len; + int8_t *limit() + { + return ptr + len; + } + + void set(int8_t *ptr_, size_t len_) + { + ptr = ptr_; + len = len_; + } + void set(const char *str) + { + ptr = (int8_t *)str; + len = strlen(str); + } + bool inBounds(const void *p); // p in [ptr, limit) + void malloc(size_t len_); + void realloc(size_t len_); + void free(); + void copyFrom(const void *ptr_, size_t len_, size_t offset = 0); + void saveFrom(const void *ptr_, size_t len_); + void saveFrom(const char *str) + { + saveFrom(str, strlen(str)); + } + void copyFrom(bytes &other, size_t offset = 0) + { + copyFrom(other.ptr, other.len, offset); + } + void saveFrom(bytes &other) + { + saveFrom(other.ptr, other.len); + } + void clear(int fill_byte = 0) + { + memset(ptr, fill_byte, len); + } + int8_t *writeTo(int8_t *bp); + bool equals(bytes &other) + { + return 0 == compareTo(other); + } + int compareTo(bytes &other); + bool contains(int8_t c) + { + return indexOf(c) >= 0; + } + int indexOf(int8_t c); + // substrings: + static bytes of(int8_t *ptr, size_t len) + { + bytes res; + res.set(ptr, len); + return res; + } + bytes slice(size_t beg, size_t end) + { + bytes res; + res.ptr = ptr + beg; + res.len = end - beg; + assert(res.len == 0 || inBounds(res.ptr) && inBounds(res.limit() - 1)); + return res; + } + // building C strings inside byte buffers: + bytes &strcat(const char *str) + { + ::strcat((char *)ptr, str); + return *this; + } + bytes &strcat(bytes &other) + { + ::strncat((char *)ptr, (char *)other.ptr, other.len); + return *this; + } + char *strval() + { + assert(strlen((char *)ptr) == len); + return (char *)ptr; + } +}; +#define BYTES_OF(var) (bytes::of((int8_t *)&(var), sizeof(var))) + +struct fillbytes +{ + bytes b; + size_t allocated; + + int8_t *base() + { + return b.ptr; + } + size_t size() + { + return b.len; + } + int8_t *limit() + { + return b.limit(); + } // logical limit + void setLimit(int8_t *lp) + { + assert(isAllocated(lp)); + b.len = lp - b.ptr; + } + int8_t *end() + { + return b.ptr + allocated; + } // physical limit + int8_t *loc(size_t o) + { + assert(o < b.len); + return b.ptr + o; + } + void init() + { + allocated = 0; + b.set(nullptr, 0); + } + void init(size_t s) + { + init(); + ensureSize(s); + } + void free() + { + if (allocated != 0) + b.free(); + allocated = 0; + } + void empty() + { + b.len = 0; + } + int8_t *grow(size_t s); // grow so that limit() += s + int getByte(uint32_t i) + { + return *loc(i) & 0xFF; + } + void addByte(int8_t x) + { + *grow(1) = x; + } + void ensureSize(size_t s); // make sure allocated >= s + void trimToSize() + { + if (allocated > size()) + b.realloc(allocated = size()); + } + bool canAppend(size_t s) + { + return allocated > b.len + s; + } + bool isAllocated(int8_t *p) + { + return p >= base() && p <= end(); + } // asserts + void set(bytes &src) + { + set(src.ptr, src.len); + } + + void set(int8_t *ptr, size_t len) + { + b.set(ptr, len); + allocated = 0; // mark as not reallocatable + } + + // block operations on resizing byte buffer: + fillbytes &append(const void *ptr_, size_t len_) + { + memcpy(grow(len_), ptr_, len_); + return (*this); + } + fillbytes &append(bytes &other) + { + return append(other.ptr, other.len); + } + fillbytes &append(const char *str) + { + return append(str, strlen(str)); + } +}; + +struct ptrlist : fillbytes +{ + typedef const void *cvptr; + int length() + { + return (int)(size() / sizeof(cvptr)); + } + cvptr *base() + { + return (cvptr *)fillbytes::base(); + } + cvptr &get(int i) + { + return *(cvptr *)loc(i * sizeof(cvptr)); + } + cvptr *limit() + { + return (cvptr *)fillbytes::limit(); + } + void add(cvptr x) + { + *(cvptr *)grow(sizeof(x)) = x; + } + void popTo(int l) + { + assert(l <= length()); + b.len = l * sizeof(cvptr); + } + int indexOf(cvptr x); + bool contains(cvptr x) + { + return indexOf(x) >= 0; + } + void freeAll(); // frees every ptr on the list, plus the list itself +}; +// Use a macro rather than mess with subtle mismatches +// between member and non-member function pointers. +#define PTRLIST_QSORT(ptrls, fn) ::qsort((ptrls).base(), (ptrls).length(), sizeof(void *), fn) + +struct intlist : fillbytes +{ + int length() + { + return (int)(size() / sizeof(int)); + } + int *base() + { + return (int *)fillbytes::base(); + } + int &get(int i) + { + return *(int *)loc(i * sizeof(int)); + } + int *limit() + { + return (int *)fillbytes::limit(); + } + void add(int x) + { + *(int *)grow(sizeof(x)) = x; + } + void popTo(int l) + { + assert(l <= length()); + b.len = l * sizeof(int); + } + int indexOf(int x); + bool contains(int x) + { + return indexOf(x) >= 0; + } +}; diff --git a/depends/pack200/src/coding.cpp b/depends/pack200/src/coding.cpp new file mode 100644 index 00000000..226ba458 --- /dev/null +++ b/depends/pack200/src/coding.cpp @@ -0,0 +1,1042 @@ +/* + * Copyright (c) 2002, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -*- C++ -*- +// Small program for unpacking specially compressed Java packages. +// John R. Rose + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <assert.h> +#include <stdint.h> + +#include "defines.h" +#include "bytes.h" +#include "utils.h" +#include "coding.h" + +#include "constants.h" +#include "unpack.h" + +extern coding basic_codings[]; + +#define CODING_PRIVATE(spec) \ + int spec_ = spec; \ + int B = CODING_B(spec_); \ + int H = CODING_H(spec_); \ + int L = 256 - H; \ + int S = CODING_S(spec_); \ + int D = CODING_D(spec_) + +#define IS_NEG_CODE(S, codeVal) ((((int)(codeVal) + 1) & ((1 << S) - 1)) == 0) + +#define DECODE_SIGN_S1(ux) (((uint32_t)(ux) >> 1) ^ -((int)(ux) & 1)) + +static int decode_sign(int S, uint32_t ux) +{ // == Coding.decodeSign32 + assert(S > 0); + uint32_t sigbits = (ux >> S); + if (IS_NEG_CODE(S, ux)) + return (int)(~sigbits); + else + return (int)(ux - sigbits); + // Note that (int)(ux-sigbits) can be negative, if ux is large enough. +} + +coding *coding::init() +{ + if (umax > 0) + return this; // already done + assert(spec != 0); // sanity + + // fill in derived fields + CODING_PRIVATE(spec); + + // Return nullptr if 'arb(BHSD)' parameter constraints are not met: + if (B < 1 || B > B_MAX) + return nullptr; + if (H < 1 || H > 256) + return nullptr; + if (S < 0 || S > 2) + return nullptr; + if (D < 0 || D > 1) + return nullptr; + if (B == 1 && H != 256) + return nullptr; // 1-byte coding must be fixed-size + if (B >= 5 && H == 256) + return nullptr; // no 5-byte fixed-size coding + + // first compute the range of the coding, in 64 bits + int64_t range = 0; + { + int64_t H_i = 1; + for (int i = 0; i < B; i++) + { + range += H_i; + H_i *= H; + } + range *= L; + range += H_i; + } + assert(range > 0); // no useless codings, please + + int this_umax; + + // now, compute min and max + if (range >= ((int64_t)1 << 32)) + { + this_umax = INT_MAX_VALUE; + this->umin = INT_MIN_VALUE; + this->max = INT_MAX_VALUE; + this->min = INT_MIN_VALUE; + } + else + { + this_umax = (range > INT_MAX_VALUE) ? INT_MAX_VALUE : (int)range - 1; + this->max = this_umax; + this->min = this->umin = 0; + if (S != 0 && range != 0) + { + int Smask = (1 << S) - 1; + int64_t maxPosCode = range - 1; + int64_t maxNegCode = range - 1; + while (IS_NEG_CODE(S, maxPosCode)) + --maxPosCode; + while (!IS_NEG_CODE(S, maxNegCode)) + --maxNegCode; + int maxPos = decode_sign(S, (uint32_t)maxPosCode); + if (maxPos < 0) + this->max = INT_MAX_VALUE; // 32-bit wraparound + else + this->max = maxPos; + if (maxNegCode < 0) + this->min = 0; // No negative codings at all. + else + this->min = decode_sign(S, (uint32_t)maxNegCode); + } + } + + assert(!(isFullRange | isSigned | isSubrange)); // init + if (min < 0) + this->isSigned = true; + if (max < INT_MAX_VALUE && range <= INT_MAX_VALUE) + this->isSubrange = true; + if (max == INT_MAX_VALUE && min == INT_MIN_VALUE) + this->isFullRange = true; + + // do this last, to reduce MT exposure (should have a membar too) + this->umax = this_umax; + + return this; +} + +coding *coding::findBySpec(int spec) +{ + for (coding *scan = &basic_codings[0];; scan++) + { + if (scan->spec == spec) + return scan->init(); + if (scan->spec == 0) + break; + } + coding *ptr = NEW(coding, 1); + if (!ptr) + return nullptr; + coding *c = ptr->initFrom(spec); + if (c == nullptr) + { + ::free(ptr); + } + else + // else caller should free it... + c->isMalloc = true; + return c; +} + +coding *coding::findBySpec(int B, int H, int S, int D) +{ + if (B < 1 || B > B_MAX) + return nullptr; + if (H < 1 || H > 256) + return nullptr; + if (S < 0 || S > 2) + return nullptr; + if (D < 0 || D > 1) + return nullptr; + return findBySpec(CODING_SPEC(B, H, S, D)); +} + +void coding::free() +{ + if (isMalloc) + { + ::free(this); + } +} + +void coding_method::reset(value_stream *state) +{ + assert(state->rp == state->rplimit); // not in mid-stream, please + // assert(this == vs0.cm); + state[0] = vs0; + if (uValues != nullptr) + { + uValues->reset(state->helper()); + } +} + +uint32_t coding::parse(byte *&rp, int B, int H) +{ + int L = 256 - H; + byte *ptr = rp; + // hand peel the i==0 part of the loop: + uint32_t b_i = *ptr++ & 0xFF; + if (B == 1 || b_i < (uint32_t)L) + { + rp = ptr; + return b_i; + } + uint32_t sum = b_i; + uint32_t H_i = H; + assert(B <= B_MAX); + for (int i = 2; i <= B_MAX; i++) + { // easy for compilers to unroll if desired + b_i = *ptr++ & 0xFF; + sum += b_i * H_i; + if (i == B || b_i < (uint32_t)L) + { + rp = ptr; + return sum; + } + H_i *= H; + } + assert(false); + return 0; +} + +uint32_t coding::parse_lgH(byte *&rp, int B, int H, int lgH) +{ + assert(H == (1 << lgH)); + int L = 256 - (1 << lgH); + byte *ptr = rp; + // hand peel the i==0 part of the loop: + uint32_t b_i = *ptr++ & 0xFF; + if (B == 1 || b_i < (uint32_t)L) + { + rp = ptr; + return b_i; + } + uint32_t sum = b_i; + uint32_t lg_H_i = lgH; + assert(B <= B_MAX); + for (int i = 2; i <= B_MAX; i++) + { // easy for compilers to unroll if desired + b_i = *ptr++ & 0xFF; + sum += b_i << lg_H_i; + if (i == B || b_i < (uint32_t)L) + { + rp = ptr; + return sum; + } + lg_H_i += lgH; + } + assert(false); + return 0; +} + +static const char ERB[] = "EOF reading band"; + +void coding::parseMultiple(byte *&rp, int N, byte *limit, int B, int H) +{ + if (N < 0) + { + unpack_abort("bad value count"); + return; + } + byte *ptr = rp; + if (B == 1 || H == 256) + { + size_t len = (size_t)N * B; + if (len / B != (size_t)N || ptr + len > limit) + { + unpack_abort(ERB); + return; + } + rp = ptr + len; + return; + } + // Note: We assume rp has enough zero-padding. + int L = 256 - H; + int n = B; + while (N > 0) + { + ptr += 1; + if (--n == 0) + { + // end of encoding at B bytes, regardless of byte value + } + else + { + int b = (ptr[-1] & 0xFF); + if (b >= L) + { + // keep going, unless we find a byte < L + continue; + } + } + // found the last byte + N -= 1; + n = B; // reset length counter + // do an error check here + if (ptr > limit) + { + unpack_abort(ERB); + return; + } + } + rp = ptr; + return; +} + +bool value_stream::hasHelper() +{ + // If my coding method is a pop-style method, + // then I need a second value stream to transmit + // unfavored values. + // This can be determined by examining fValues. + return cm->fValues != nullptr; +} + +void value_stream::init(byte *rp_, byte *rplimit_, coding *defc) +{ + rp = rp_; + rplimit = rplimit_; + sum = 0; + cm = nullptr; // no need in the simple case + setCoding(defc); +} + +void value_stream::setCoding(coding *defc) +{ + if (defc == nullptr) + { + unpack_abort("bad coding"); + defc = coding::findByIndex(_meta_canon_min); // random pick for recovery + } + + c = (*defc); + + // choose cmk + cmk = cmk_ERROR; + switch (c.spec) + { + case BYTE1_spec: + cmk = cmk_BYTE1; + break; + case CHAR3_spec: + cmk = cmk_CHAR3; + break; + case UNSIGNED5_spec: + cmk = cmk_UNSIGNED5; + break; + case DELTA5_spec: + cmk = cmk_DELTA5; + break; + case BCI5_spec: + cmk = cmk_BCI5; + break; + case BRANCH5_spec: + cmk = cmk_BRANCH5; + break; + default: + if (c.D() == 0) + { + switch (c.S()) + { + case 0: + cmk = cmk_BHS0; + break; + case 1: + cmk = cmk_BHS1; + break; + default: + cmk = cmk_BHS; + break; + } + } + else + { + if (c.S() == 1) + { + if (c.isFullRange) + cmk = cmk_BHS1D1full; + if (c.isSubrange) + cmk = cmk_BHS1D1sub; + } + if (cmk == cmk_ERROR) + cmk = cmk_BHSD1; + } + } +} + +static int getPopValue(value_stream *self, uint32_t uval) +{ + if (uval > 0) + { + // note that the initial parse performed a range check + assert(uval <= (uint32_t)self->cm->fVlength); + return self->cm->fValues[uval - 1]; + } + else + { + // take an unfavored value + return self->helper()->getInt(); + } +} + +int coding::sumInUnsignedRange(int x, int y) +{ + assert(isSubrange); + int range = (int)(umax + 1); + assert(range > 0); + x += y; + if (x != (int)((int64_t)(x - y) + (int64_t)y)) + { + // 32-bit overflow interferes with range reduction. + // Back off from the overflow by adding a multiple of range: + if (x < 0) + { + x -= range; + assert(x >= 0); + } + else + { + x += range; + assert(x < 0); + } + } + if (x < 0) + { + x += range; + if (x >= 0) + return x; + } + else if (x >= range) + { + x -= range; + if (x < range) + return x; + } + else + { + // in range + return x; + } + // do it the hard way + x %= range; + if (x < 0) + x += range; + return x; +} + +static int getDeltaValue(value_stream *self, uint32_t uval, bool isSubrange) +{ + assert((uint32_t)(self->c.isSubrange) == (uint32_t)isSubrange); + assert(self->c.isSubrange | self->c.isFullRange); + if (isSubrange) + return self->sum = self->c.sumInUnsignedRange(self->sum, (int)uval); + else + return self->sum += (int)uval; +} + +bool value_stream::hasValue() +{ + if (rp < rplimit) + return true; + if (cm == nullptr) + return false; + if (cm->next == nullptr) + return false; + cm->next->reset(this); + return hasValue(); +} + +int value_stream::getInt() +{ + if (rp >= rplimit) + { + // Advance to next coding segment. + if (rp > rplimit || cm == nullptr || cm->next == nullptr) + { + // Must perform this check and throw an exception on bad input. + unpack_abort(ERB); + return 0; + } + cm->next->reset(this); + return getInt(); + } + + CODING_PRIVATE(c.spec); + uint32_t uval; + enum + { + B5 = 5, + B3 = 3, + H128 = 128, + H64 = 64, + H4 = 4 + }; + switch (cmk) + { + case cmk_BHS: + assert(D == 0); + uval = coding::parse(rp, B, H); + if (S == 0) + return (int)uval; + return decode_sign(S, uval); + + case cmk_BHS0: + assert(S == 0 && D == 0); + uval = coding::parse(rp, B, H); + return (int)uval; + + case cmk_BHS1: + assert(S == 1 && D == 0); + uval = coding::parse(rp, B, H); + return DECODE_SIGN_S1(uval); + + case cmk_BYTE1: + assert(c.spec == BYTE1_spec); + assert(B == 1 && H == 256 && S == 0 && D == 0); + return *rp++ & 0xFF; + + case cmk_CHAR3: + assert(c.spec == CHAR3_spec); + assert(B == B3 && H == H128 && S == 0 && D == 0); + return coding::parse_lgH(rp, B3, H128, 7); + + case cmk_UNSIGNED5: + assert(c.spec == UNSIGNED5_spec); + assert(B == B5 && H == H64 && S == 0 && D == 0); + return coding::parse_lgH(rp, B5, H64, 6); + + case cmk_BHSD1: + assert(D == 1); + uval = coding::parse(rp, B, H); + if (S != 0) + uval = (uint32_t)decode_sign(S, uval); + return getDeltaValue(this, uval, (bool)c.isSubrange); + + case cmk_BHS1D1full: + assert(S == 1 && D == 1 && c.isFullRange); + uval = coding::parse(rp, B, H); + uval = (uint32_t)DECODE_SIGN_S1(uval); + return getDeltaValue(this, uval, false); + + case cmk_BHS1D1sub: + assert(S == 1 && D == 1 && c.isSubrange); + uval = coding::parse(rp, B, H); + uval = (uint32_t)DECODE_SIGN_S1(uval); + return getDeltaValue(this, uval, true); + + case cmk_DELTA5: + assert(c.spec == DELTA5_spec); + assert(B == B5 && H == H64 && S == 1 && D == 1 && c.isFullRange); + uval = coding::parse_lgH(rp, B5, H64, 6); + sum += DECODE_SIGN_S1(uval); + return sum; + + case cmk_BCI5: + assert(c.spec == BCI5_spec); + assert(B == B5 && H == H4 && S == 0 && D == 0); + return coding::parse_lgH(rp, B5, H4, 2); + + case cmk_BRANCH5: + assert(c.spec == BRANCH5_spec); + assert(B == B5 && H == H4 && S == 2 && D == 0); + uval = coding::parse_lgH(rp, B5, H4, 2); + return decode_sign(S, uval); + + case cmk_pop: + uval = coding::parse(rp, B, H); + if (S != 0) + { + uval = (uint32_t)decode_sign(S, uval); + } + if (D != 0) + { + assert(c.isSubrange | c.isFullRange); + if (c.isSubrange) + sum = c.sumInUnsignedRange(sum, (int)uval); + else + sum += (int)uval; + uval = (uint32_t)sum; + } + return getPopValue(this, uval); + + case cmk_pop_BHS0: + assert(S == 0 && D == 0); + uval = coding::parse(rp, B, H); + return getPopValue(this, uval); + + case cmk_pop_BYTE1: + assert(c.spec == BYTE1_spec); + assert(B == 1 && H == 256 && S == 0 && D == 0); + return getPopValue(this, *rp++ & 0xFF); + + default: + break; + } + assert(false); + return 0; +} + +static int moreCentral(int x, int y) +{ // used to find end of Pop.{F} + // Suggested implementation from the Pack200 specification: + uint32_t kx = (x >> 31) ^ (x << 1); + uint32_t ky = (y >> 31) ^ (y << 1); + return (kx < ky ? x : y); +} +// static maybe_inline +// int moreCentral2(int x, int y, int min) { +// // Strict implementation of buggy 150.7 specification. +// // The bug is that the spec. says absolute-value ties are broken +// // in favor of positive numbers, but the suggested implementation +// // (also mentioned in the spec.) breaks ties in favor of negative numbers. +// if ((x + y) != 0) +// return min; +// else +// // return the other value, which breaks a tie in the positive direction +// return (x > y)? x: y; +//} + +static const byte *no_meta[] = {nullptr}; +#define NO_META (*(byte **)no_meta) +enum +{ + POP_FAVORED_N = -2 +}; + +// mode bits +#define DISABLE_RUN 1 // used immediately inside ACodee +#define DISABLE_POP 2 // used recursively in all pop sub-bands + +// This function knows all about meta-coding. +void coding_method::init(byte *&band_rp, byte *band_limit, byte *&meta_rp, int mode, + coding *defc, int N, intlist *valueSink) +{ + assert(N != 0); + + assert(u != nullptr); // must be pre-initialized + // if (u == nullptr) u = unpacker::current(); // expensive + + int op = (meta_rp == nullptr) ? _meta_default : (*meta_rp++ & 0xFF); + coding *foundc = nullptr; + coding *to_free = nullptr; + + if (op == _meta_default) + { + foundc = defc; + // and fall through + } + else if (op >= _meta_canon_min && op <= _meta_canon_max) + { + foundc = coding::findByIndex(op); + // and fall through + } + else if (op == _meta_arb) + { + int args = (*meta_rp++ & 0xFF); + // args = (D:[0..1] + 2*S[0..2] + 8*(B:[1..5]-1)) + int D = ((args >> 0) & 1); + int S = ((args >> 1) & 3); + int B = ((args >> 3) & -1) + 1; + // & (H[1..256]-1) + int H = (*meta_rp++ & 0xFF) + 1; + foundc = coding::findBySpec(B, H, S, D); + to_free = foundc; // findBySpec may dynamically allocate + if (foundc == nullptr) + { + unpack_abort("illegal arbitrary coding"); + return; + } + // and fall through + } + else if (op >= _meta_run && op < _meta_pop) + { + int args = (op - _meta_run); + // args: KX:[0..3] + 4*(KBFlag:[0..1]) + 8*(ABDef:[0..2]) + int KX = ((args >> 0) & 3); + int KBFlag = ((args >> 2) & 1); + int ABDef = ((args >> 3) & -1); + assert(ABDef <= 2); + // & KB: one of [0..255] if KBFlag=1 + int KB = (!KBFlag ? 3 : (*meta_rp++ & 0xFF)); + int K = (KB + 1) << (KX * 4); + int N2 = (N >= 0) ? N - K : N; + if (N == 0 || (N2 <= 0 && N2 != N)) + { + unpack_abort("illegal run encoding"); + } + if ((mode & DISABLE_RUN) != 0) + { + unpack_abort("illegal nested run encoding"); + } + + // & Enc{ ACode } if ADef=0 (ABDef != 1) + // No direct nesting of 'run' in ACode, but in BCode it's OK. + int disRun = mode | DISABLE_RUN; + if (ABDef == 1) + { + this->init(band_rp, band_limit, NO_META, disRun, defc, K, valueSink); + } + else + { + this->init(band_rp, band_limit, meta_rp, disRun, defc, K, valueSink); + } + + // & Enc{ BCode } if BDef=0 (ABDef != 2) + coding_method *tail = U_NEW(coding_method, 1); + if (!tail) + return; + tail->u = u; + + // The 'run' codings may be nested indirectly via 'pop' codings. + // This means that this->next may already be filled in, if + // ACode was of type 'pop' with a 'run' token coding. + // No problem: Just chain the upcoming BCode onto the end. + for (coding_method *self = this;; self = self->next) + { + if (self->next == nullptr) + { + self->next = tail; + break; + } + } + + if (ABDef == 2) + { + tail->init(band_rp, band_limit, NO_META, mode, defc, N2, valueSink); + } + else + { + tail->init(band_rp, band_limit, meta_rp, mode, defc, N2, valueSink); + } + // Note: The preceding calls to init should be tail-recursive. + + return; // done; no falling through + } + else if (op >= _meta_pop && op < _meta_limit) + { + int args = (op - _meta_pop); + // args: (FDef:[0..1]) + 2*UDef:[0..1] + 4*(TDefL:[0..11]) + int FDef = ((args >> 0) & 1); + int UDef = ((args >> 1) & 1); + int TDefL = ((args >> 2) & -1); + assert(TDefL <= 11); + int TDef = (TDefL > 0); + int TL = (TDefL <= 6) ? (2 << TDefL) : (256 - (4 << (11 - TDefL))); + int TH = (256 - TL); + if (N <= 0) + { + unpack_abort("illegal pop encoding"); + } + if ((mode & DISABLE_POP) != 0) + { + unpack_abort("illegal nested pop encoding"); + } + + // No indirect nesting of 'pop', but 'run' is OK. + int disPop = DISABLE_POP; + + // & Enc{ FCode } if FDef=0 + int FN = POP_FAVORED_N; + assert(valueSink == nullptr); + intlist fValueSink; + fValueSink.init(); + coding_method fval; + BYTES_OF(fval).clear(); + fval.u = u; + if (FDef != 0) + { + fval.init(band_rp, band_limit, NO_META, disPop, defc, FN, &fValueSink); + } + else + { + fval.init(band_rp, band_limit, meta_rp, disPop, defc, FN, &fValueSink); + } + bytes fvbuf; + fValues = (u->saveTo(fvbuf, fValueSink.b), (int *)fvbuf.ptr); + fVlength = fValueSink.length(); // i.e., the parameter K + fValueSink.free(); + + // Skip the first {F} run in all subsequent passes. + // The next call to this->init(...) will set vs0.rp to point after the {F}. + + // & Enc{ TCode } if TDef=0 (TDefL==0) + if (TDef != 0) + { + coding *tcode = coding::findBySpec(1, 256); // BYTE1 + // find the most narrowly sufficient code: + for (int B = 2; B <= B_MAX; B++) + { + if (fVlength <= tcode->umax) + break; // found it + tcode->free(); + tcode = coding::findBySpec(B, TH); + if (!tcode) + return; + } + if (!(fVlength <= tcode->umax)) + { + unpack_abort("pop.L value too small"); + } + this->init(band_rp, band_limit, NO_META, disPop, tcode, N, nullptr); + tcode->free(); + } + else + { + this->init(band_rp, band_limit, meta_rp, disPop, defc, N, nullptr); + } + + // Count the number of zero tokens right now. + // Also verify that they are in bounds. + int UN = 0; // one {U} for each zero in {T} + value_stream vs = vs0; + for (int i = 0; i < N; i++) + { + uint32_t val = vs.getInt(); + if (val == 0) + UN += 1; + if (!(val <= (uint32_t)fVlength)) + { + unpack_abort("pop token out of range"); + } + } + vs.done(); + + // & Enc{ UCode } if UDef=0 + if (UN != 0) + { + uValues = U_NEW(coding_method, 1); + if (uValues == nullptr) + return; + uValues->u = u; + if (UDef != 0) + { + uValues->init(band_rp, band_limit, NO_META, disPop, defc, UN, nullptr); + } + else + { + uValues->init(band_rp, band_limit, meta_rp, disPop, defc, UN, nullptr); + } + } + else + { + if (UDef == 0) + { + int uop = (*meta_rp++ & 0xFF); + if (uop > _meta_canon_max) + // %%% Spec. requires the more strict (uop != _meta_default). + unpack_abort("bad meta-coding for empty pop/U"); + } + } + + // Bug fix for 6259542 + // Last of all, adjust vs0.cmk to the 'pop' flavor + for (coding_method *self = this; self != nullptr; self = self->next) + { + coding_method_kind cmk2 = cmk_pop; + switch (self->vs0.cmk) + { + case cmk_BHS0: + cmk2 = cmk_pop_BHS0; + break; + case cmk_BYTE1: + cmk2 = cmk_pop_BYTE1; + break; + default: + break; + } + self->vs0.cmk = cmk2; + if (self != this) + { + assert(self->fValues == nullptr); // no double init + self->fValues = this->fValues; + self->fVlength = this->fVlength; + assert(self->uValues == nullptr); // must stay nullptr + } + } + + return; // done; no falling through + } + else + { + unpack_abort("bad meta-coding"); + } + + // Common code here skips a series of values with one coding. + assert(foundc != nullptr); + + assert(vs0.cmk == cmk_ERROR); // no garbage, please + assert(vs0.rp == nullptr); // no garbage, please + assert(vs0.rplimit == nullptr); // no garbage, please + assert(vs0.sum == 0); // no garbage, please + + vs0.init(band_rp, band_limit, foundc); + + // Done with foundc. Free if necessary. + if (to_free != nullptr) + { + to_free->free(); + to_free = nullptr; + } + foundc = nullptr; + + coding &c = vs0.c; + CODING_PRIVATE(c.spec); + // assert sane N + assert((uint32_t)N < INT_MAX_VALUE || N == POP_FAVORED_N); + + // Look at the values, or at least skip over them quickly. + if (valueSink == nullptr) + { + // Skip and ignore values in the first pass. + c.parseMultiple(band_rp, N, band_limit, B, H); + } + else if (N >= 0) + { + // Pop coding, {F} sequence, initial run of values... + assert((mode & DISABLE_POP) != 0); + value_stream vs = vs0; + for (int n = 0; n < N; n++) + { + int val = vs.getInt(); + valueSink->add(val); + } + band_rp = vs.rp; + } + else + { + // Pop coding, {F} sequence, final run of values... + assert((mode & DISABLE_POP) != 0); + assert(N == POP_FAVORED_N); + int min = INT_MIN_VALUE; // farthest from the center + // min2 is based on the buggy specification of centrality in version 150.7 + // no known implementations transmit this value, but just in case... + // int min2 = INT_MIN_VALUE; + int last = 0; + // if there were initial runs, find the potential sentinels in them: + for (int i = 0; i < valueSink->length(); i++) + { + last = valueSink->get(i); + min = moreCentral(min, last); + // min2 = moreCentral2(min2, last, min); + } + value_stream vs = vs0; + for (;;) + { + int val = vs.getInt(); + if (valueSink->length() > 0 && (val == last || val == min)) //|| val == min2 + break; + valueSink->add(val); + last = val; + min = moreCentral(min, last); + // min2 = moreCentral2(min2, last, min); + } + band_rp = vs.rp; + } + + // Get an accurate upper limit now. + vs0.rplimit = band_rp; + vs0.cm = this; + + return; // success +} + +coding basic_codings[] = { + // This one is not a usable irregular coding, but is used by cp_Utf8_chars. + CODING_INIT(3, 128, 0, 0), + + // Fixed-length codings: + CODING_INIT(1, 256, 0, 0), CODING_INIT(1, 256, 1, 0), CODING_INIT(1, 256, 0, 1), + CODING_INIT(1, 256, 1, 1), CODING_INIT(2, 256, 0, 0), CODING_INIT(2, 256, 1, 0), + CODING_INIT(2, 256, 0, 1), CODING_INIT(2, 256, 1, 1), CODING_INIT(3, 256, 0, 0), + CODING_INIT(3, 256, 1, 0), CODING_INIT(3, 256, 0, 1), CODING_INIT(3, 256, 1, 1), + CODING_INIT(4, 256, 0, 0), CODING_INIT(4, 256, 1, 0), CODING_INIT(4, 256, 0, 1), + CODING_INIT(4, 256, 1, 1), + + // Full-range variable-length codings: + CODING_INIT(5, 4, 0, 0), CODING_INIT(5, 4, 1, 0), CODING_INIT(5, 4, 2, 0), + CODING_INIT(5, 16, 0, 0), CODING_INIT(5, 16, 1, 0), CODING_INIT(5, 16, 2, 0), + CODING_INIT(5, 32, 0, 0), CODING_INIT(5, 32, 1, 0), CODING_INIT(5, 32, 2, 0), + CODING_INIT(5, 64, 0, 0), CODING_INIT(5, 64, 1, 0), CODING_INIT(5, 64, 2, 0), + CODING_INIT(5, 128, 0, 0), CODING_INIT(5, 128, 1, 0), CODING_INIT(5, 128, 2, 0), + CODING_INIT(5, 4, 0, 1), CODING_INIT(5, 4, 1, 1), CODING_INIT(5, 4, 2, 1), + CODING_INIT(5, 16, 0, 1), CODING_INIT(5, 16, 1, 1), CODING_INIT(5, 16, 2, 1), + CODING_INIT(5, 32, 0, 1), CODING_INIT(5, 32, 1, 1), CODING_INIT(5, 32, 2, 1), + CODING_INIT(5, 64, 0, 1), CODING_INIT(5, 64, 1, 1), CODING_INIT(5, 64, 2, 1), + CODING_INIT(5, 128, 0, 1), CODING_INIT(5, 128, 1, 1), CODING_INIT(5, 128, 2, 1), + + // Variable length subrange codings: + CODING_INIT(2, 192, 0, 0), CODING_INIT(2, 224, 0, 0), CODING_INIT(2, 240, 0, 0), + CODING_INIT(2, 248, 0, 0), CODING_INIT(2, 252, 0, 0), CODING_INIT(2, 8, 0, 1), + CODING_INIT(2, 8, 1, 1), CODING_INIT(2, 16, 0, 1), CODING_INIT(2, 16, 1, 1), + CODING_INIT(2, 32, 0, 1), CODING_INIT(2, 32, 1, 1), CODING_INIT(2, 64, 0, 1), + CODING_INIT(2, 64, 1, 1), CODING_INIT(2, 128, 0, 1), CODING_INIT(2, 128, 1, 1), + CODING_INIT(2, 192, 0, 1), CODING_INIT(2, 192, 1, 1), CODING_INIT(2, 224, 0, 1), + CODING_INIT(2, 224, 1, 1), CODING_INIT(2, 240, 0, 1), CODING_INIT(2, 240, 1, 1), + CODING_INIT(2, 248, 0, 1), CODING_INIT(2, 248, 1, 1), CODING_INIT(3, 192, 0, 0), + CODING_INIT(3, 224, 0, 0), CODING_INIT(3, 240, 0, 0), CODING_INIT(3, 248, 0, 0), + CODING_INIT(3, 252, 0, 0), CODING_INIT(3, 8, 0, 1), CODING_INIT(3, 8, 1, 1), + CODING_INIT(3, 16, 0, 1), CODING_INIT(3, 16, 1, 1), CODING_INIT(3, 32, 0, 1), + CODING_INIT(3, 32, 1, 1), CODING_INIT(3, 64, 0, 1), CODING_INIT(3, 64, 1, 1), + CODING_INIT(3, 128, 0, 1), CODING_INIT(3, 128, 1, 1), CODING_INIT(3, 192, 0, 1), + CODING_INIT(3, 192, 1, 1), CODING_INIT(3, 224, 0, 1), CODING_INIT(3, 224, 1, 1), + CODING_INIT(3, 240, 0, 1), CODING_INIT(3, 240, 1, 1), CODING_INIT(3, 248, 0, 1), + CODING_INIT(3, 248, 1, 1), CODING_INIT(4, 192, 0, 0), CODING_INIT(4, 224, 0, 0), + CODING_INIT(4, 240, 0, 0), CODING_INIT(4, 248, 0, 0), CODING_INIT(4, 252, 0, 0), + CODING_INIT(4, 8, 0, 1), CODING_INIT(4, 8, 1, 1), CODING_INIT(4, 16, 0, 1), + CODING_INIT(4, 16, 1, 1), CODING_INIT(4, 32, 0, 1), CODING_INIT(4, 32, 1, 1), + CODING_INIT(4, 64, 0, 1), CODING_INIT(4, 64, 1, 1), CODING_INIT(4, 128, 0, 1), + CODING_INIT(4, 128, 1, 1), CODING_INIT(4, 192, 0, 1), CODING_INIT(4, 192, 1, 1), + CODING_INIT(4, 224, 0, 1), CODING_INIT(4, 224, 1, 1), CODING_INIT(4, 240, 0, 1), + CODING_INIT(4, 240, 1, 1), CODING_INIT(4, 248, 0, 1), CODING_INIT(4, 248, 1, 1), + CODING_INIT(0, 0, 0, 0)}; +#define BASIC_INDEX_LIMIT (int)(sizeof(basic_codings) / sizeof(basic_codings[0]) - 1) + +coding *coding::findByIndex(int idx) +{ + int index_limit = BASIC_INDEX_LIMIT; + assert(_meta_canon_min == 1 && _meta_canon_max + 1 == index_limit); + + if (idx >= _meta_canon_min && idx <= _meta_canon_max) + return basic_codings[idx].init(); + else + return nullptr; +} diff --git a/depends/pack200/src/coding.h b/depends/pack200/src/coding.h new file mode 100644 index 00000000..f9bd6ca2 --- /dev/null +++ b/depends/pack200/src/coding.h @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +struct unpacker; + +#define INT_MAX_VALUE ((int)0x7FFFFFFF) +#define INT_MIN_VALUE ((int)0x80000000) + +#define CODING_SPEC(B, H, S, D) ((B) << 20 | (H) << 8 | (S) << 4 | (D) << 0) +#define CODING_B(x) ((x) >> 20 & 0xF) +#define CODING_H(x) ((x) >> 8 & 0xFFF) +#define CODING_S(x) ((x) >> 4 & 0xF) +#define CODING_D(x) ((x) >> 0 & 0xF) + +#define CODING_INIT(B, H, S, D) \ + { \ + CODING_SPEC(B, H, S, D), 0, 0, 0, 0, 0, 0, 0, 0 \ + } + +// For debugging purposes, some compilers do not like this and will complain. +// #define long do_not_use_C_long_types_use_jlong_or_int +// Use of the type "long" is problematic, do not use it. + +struct coding +{ + int spec; // B,H,S,D + + // Handy values derived from the spec: + int B() + { + return CODING_B(spec); + } + int H() + { + return CODING_H(spec); + } + int S() + { + return CODING_S(spec); + } + int D() + { + return CODING_D(spec); + } + int L() + { + return 256 - CODING_H(spec); + } + int min, max; + int umin, umax; + char isSigned, isSubrange, isFullRange, isMalloc; + + coding *init(); // returns self or nullptr if error + coding *initFrom(int spec_) + { + assert(this->spec == 0); + this->spec = spec_; + return init(); + } + + static coding *findBySpec(int spec); + static coding *findBySpec(int B, int H, int S = 0, int D = 0); + static coding *findByIndex(int irregularCodingIndex); + + static uint32_t parse(byte *&rp, int B, int H); + static uint32_t parse_lgH(byte *&rp, int B, int H, int lgH); + static void parseMultiple(byte *&rp, int N, byte *limit, int B, int H); + + uint32_t parse(byte *&rp) + { + return parse(rp, CODING_B(spec), CODING_H(spec)); + } + void parseMultiple(byte *&rp, int N, byte *limit) + { + parseMultiple(rp, N, limit, CODING_B(spec), CODING_H(spec)); + } + + bool canRepresent(int x) + { + return (x >= min && x <= max); + } + bool canRepresentUnsigned(int x) + { + return (x >= umin && x <= umax); + } + + int sumInUnsignedRange(int x, int y); + + int readFrom(byte *&rpVar, int *dbase); + void readArrayFrom(byte *&rpVar, int *dbase, int length, int *values); + void skipArrayFrom(byte *&rpVar, int length) + { + readArrayFrom(rpVar, (int *)NULL, length, (int *)NULL); + } + + void free(); // free self if isMalloc +}; + +enum coding_method_kind +{ + cmk_ERROR, + cmk_BHS, + cmk_BHS0, + cmk_BHS1, + cmk_BHSD1, + cmk_BHS1D1full, // isFullRange + cmk_BHS1D1sub, // isSubRange + + // special cases hand-optimized (~50% of all decoded values) + cmk_BYTE1, //(1,256) 6% + cmk_CHAR3, //(3,128) 7% + cmk_UNSIGNED5, //(5,64) 13% + cmk_DELTA5, //(5,64,1,1) 5% + cmk_BCI5, //(5,4) 18% + cmk_BRANCH5, //(5,4,2) 4% + // cmk_UNSIGNED5H16, //(5,16) 5% + // cmk_UNSIGNED2H4, //(2,4) 6% + // cmk_DELTA4H8, //(4,8,1,1) 10% + // cmk_DELTA3H16, //(3,16,1,1) 9% + cmk_BHS_LIMIT, + cmk_pop, + cmk_pop_BHS0, + cmk_pop_BYTE1, + cmk_pop_LIMIT, + cmk_LIMIT +}; + +enum +{ + BYTE1_spec = CODING_SPEC(1, 256, 0, 0), + CHAR3_spec = CODING_SPEC(3, 128, 0, 0), + UNSIGNED4_spec = CODING_SPEC(4, 256, 0, 0), + UNSIGNED5_spec = CODING_SPEC(5, 64, 0, 0), + SIGNED5_spec = CODING_SPEC(5, 64, 1, 0), + DELTA5_spec = CODING_SPEC(5, 64, 1, 1), + UDELTA5_spec = CODING_SPEC(5, 64, 0, 1), + MDELTA5_spec = CODING_SPEC(5, 64, 2, 1), + BCI5_spec = CODING_SPEC(5, 4, 0, 0), + BRANCH5_spec = CODING_SPEC(5, 4, 2, 0) +}; + +enum +{ + B_MAX = 5, + C_SLOP = B_MAX * 10 +}; + +struct coding_method; + +// iterator under the control of a meta-coding +struct value_stream +{ + // current coding of values or values + coding c; // B,H,S,D,etc. + coding_method_kind cmk; // type of decoding needed + byte *rp; // read pointer + byte *rplimit; // final value of read pointer + int sum; // partial sum of all values so far (D=1 only) + coding_method *cm; // coding method that defines this stream + + void init(byte *band_rp, byte *band_limit, coding *defc); + void init(byte *band_rp, byte *band_limit, int spec) + { + init(band_rp, band_limit, coding::findBySpec(spec)); + } + + void setCoding(coding *c); + void setCoding(int spec) + { + setCoding(coding::findBySpec(spec)); + } + + // Parse and decode a single value. + int getInt(); + + // Parse and decode a single byte, with no error checks. + int getByte() + { + assert(cmk == cmk_BYTE1); + assert(rp < rplimit); + return *rp++ & 0xFF; + } + + // Used only for asserts. + bool hasValue(); + + void done() + { + assert(!hasValue()); + } + + // Sometimes a value stream has an auxiliary (but there are never two). + value_stream *helper() + { + assert(hasHelper()); + return this + 1; + } + bool hasHelper(); +}; + +struct coding_method +{ + value_stream vs0; // initial state snapshot (vs.meta==this) + + coding_method *next; // what to do when we run out of bytes + + // these fields are used for pop codes only: + int *fValues; // favored value array + int fVlength; // maximum favored value token + coding_method *uValues; // unfavored value stream + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + // Initialize a value stream. + void reset(value_stream *state); + + // Parse a band header, size a band, and initialize for further action. + // band_rp advances (but not past band_limit), and meta_rp advances. + // The mode gives context, such as "inside a pop". + // The defc and N are the incoming parameters to a meta-coding. + // The value sink is used to collect output values, when desired. + void init(byte *&band_rp, byte *band_limit, byte *&meta_rp, int mode, coding *defc, int N, + intlist *valueSink); +}; diff --git a/depends/pack200/src/constants.h b/depends/pack200/src/constants.h new file mode 100644 index 00000000..2cc14b7d --- /dev/null +++ b/depends/pack200/src/constants.h @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + Java Class Version numbers history + 1.0 to 1.3.X 45,3 + 1.4 to 1.4.X 46,0 + 1.5 to 1.5.X 49,0 + 1.6 to 1.5.x 50,0 NOTE Assumed for now +*/ + +// classfile constants +#define JAVA_MAGIC 0xCAFEBABE +#define JAVA_MIN_MAJOR_VERSION 45 +#define JAVA_MIN_MINOR_VERSION 3 +#define JAVA5_MAX_MAJOR_VERSION 49 +#define JAVA5_MAX_MINOR_VERSION 0 +// NOTE: Assume for now +#define JAVA6_MAX_MAJOR_VERSION 50 +#define JAVA6_MAX_MINOR_VERSION 0 + +// package file constants +#define JAVA_PACKAGE_MAGIC 0xCAFED00D +#define JAVA5_PACKAGE_MAJOR_VERSION 150 +#define JAVA5_PACKAGE_MINOR_VERSION 7 + +#define JAVA6_PACKAGE_MAJOR_VERSION 160 +#define JAVA6_PACKAGE_MINOR_VERSION 1 + +// magic number for gzip streams (for processing pack200-gzip data) +#define GZIP_MAGIC 0x1F8B0800 +#define GZIP_MAGIC_MASK 0xFFFFFF00 // last \bchar\b is variable "flg" field + +enum +{ + CONSTANT_None, + CONSTANT_Utf8, + CONSTANT_unused2, /* unused, was Unicode */ + CONSTANT_Integer, + CONSTANT_Float, + CONSTANT_Long, + CONSTANT_Double, + CONSTANT_Class, + CONSTANT_String, + CONSTANT_Fieldref, + CONSTANT_Methodref, + CONSTANT_InterfaceMethodref, + CONSTANT_NameandType, + CONSTANT_Signature = 13, + CONSTANT_All = 14, + CONSTANT_Limit = 15, + CONSTANT_NONE = 0, + CONSTANT_Literal = 20, // pseudo-tag for debugging + CONSTANT_Member = 21, // pseudo-tag for debugging + SUBINDEX_BIT = 64, // combined with CONSTANT_xxx for ixTag + ACC_STATIC = 0x0008, + ACC_IC_LONG_FORM = (1 << 16), // for ic_flags + CLASS_ATTR_SourceFile = 17, + CLASS_ATTR_EnclosingMethod = 18, + CLASS_ATTR_InnerClasses = 23, + CLASS_ATTR_ClassFile_version = 24, + FIELD_ATTR_ConstantValue = 17, + METHOD_ATTR_Code = 17, + METHOD_ATTR_Exceptions = 18, + METHOD_ATTR_RuntimeVisibleParameterAnnotations = 23, + METHOD_ATTR_RuntimeInvisibleParameterAnnotations = 24, + METHOD_ATTR_AnnotationDefault = 25, + CODE_ATTR_StackMapTable = 0, + CODE_ATTR_LineNumberTable = 1, + CODE_ATTR_LocalVariableTable = 2, + CODE_ATTR_LocalVariableTypeTable = 3, + // X_ATTR_Synthetic = 12, // ACC_SYNTHETIC; not predefined + X_ATTR_Signature = 19, + X_ATTR_Deprecated = 20, + X_ATTR_RuntimeVisibleAnnotations = 21, + X_ATTR_RuntimeInvisibleAnnotations = 22, + X_ATTR_OVERFLOW = 16, + X_ATTR_LIMIT_NO_FLAGS_HI = 32, + X_ATTR_LIMIT_FLAGS_HI = 63, + +#define O_ATTR_DO(F) \ + F(X_ATTR_OVERFLOW, 01) \ + /*(end)*/ +#define X_ATTR_DO(F) \ + O_ATTR_DO(F) F(X_ATTR_Signature, Signature) F(X_ATTR_Deprecated, Deprecated) \ + F(X_ATTR_RuntimeVisibleAnnotations, RuntimeVisibleAnnotations) \ + F(X_ATTR_RuntimeInvisibleAnnotations, RuntimeInvisibleAnnotations) \ + /*F(X_ATTR_Synthetic,Synthetic)*/ \ + /*(end)*/ +#define CLASS_ATTR_DO(F) \ + F(CLASS_ATTR_SourceFile, SourceFile) F(CLASS_ATTR_InnerClasses, InnerClasses) \ + F(CLASS_ATTR_EnclosingMethod, EnclosingMethod) F(CLASS_ATTR_ClassFile_version, 02) \ + /*(end)*/ +#define FIELD_ATTR_DO(F) \ + F(FIELD_ATTR_ConstantValue, ConstantValue) \ + /*(end)*/ +#define METHOD_ATTR_DO(F) \ + F(METHOD_ATTR_Code, Code) F(METHOD_ATTR_Exceptions, Exceptions) \ + F(METHOD_ATTR_RuntimeVisibleParameterAnnotations, RuntimeVisibleParameterAnnotations) \ + F(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, \ + RuntimeInvisibleParameterAnnotations) \ + F(METHOD_ATTR_AnnotationDefault, AnnotationDefault) \ + /*(end)*/ +#define CODE_ATTR_DO(F) \ + F(CODE_ATTR_StackMapTable, StackMapTable) F(CODE_ATTR_LineNumberTable, LineNumberTable) \ + F(CODE_ATTR_LocalVariableTable, LocalVariableTable) \ + F(CODE_ATTR_LocalVariableTypeTable, LocalVariableTypeTable) \ + /*(end)*/ +#define ALL_ATTR_DO(F) \ + X_ATTR_DO(F) CLASS_ATTR_DO(F) FIELD_ATTR_DO(F) METHOD_ATTR_DO(F) CODE_ATTR_DO(F) \ + /*(end)*/ + + // attribute "context types" + ATTR_CONTEXT_CLASS = 0, + ATTR_CONTEXT_FIELD = 1, + ATTR_CONTEXT_METHOD = 2, + ATTR_CONTEXT_CODE = 3, + ATTR_CONTEXT_LIMIT = 4, + + // constants for parsed layouts (stored in band::le_kind) + EK_NONE = 0, // not a layout element + EK_INT = 'I', // B H I SH etc., also FH etc. + EK_BCI = 'P', // PH etc. + EK_BCID = 'Q', // POH etc. + EK_BCO = 'O', // OH etc. + EK_REPL = 'N', // NH[...] etc. + EK_REF = 'R', // RUH, RUNH, KQH, etc. + EK_UN = 'T', // TB(...)[...] etc. + EK_CASE = 'K', // (...)[...] etc. + EK_CALL = '(', // (0), (1), etc. + EK_CBLE = '[', // [...][...] etc. + NO_BAND_INDEX = -1, + + // File option bits, from LSB in ascending bit position. + FO_DEFLATE_HINT = 1 << 0, + FO_IS_CLASS_STUB = 1 << 1, + + // Archive option bits, from LSB in ascending bit position: + AO_HAVE_SPECIAL_FORMATS = 1 << 0, + AO_HAVE_CP_NUMBERS = 1 << 1, + AO_HAVE_ALL_CODE_FLAGS = 1 << 2, + AO_3_UNUSED_MBZ = 1 << 3, + AO_HAVE_FILE_HEADERS = 1 << 4, + AO_DEFLATE_HINT = 1 << 5, + AO_HAVE_FILE_MODTIME = 1 << 6, + AO_HAVE_FILE_OPTIONS = 1 << 7, + AO_HAVE_FILE_SIZE_HI = 1 << 8, + AO_HAVE_CLASS_FLAGS_HI = 1 << 9, + AO_HAVE_FIELD_FLAGS_HI = 1 << 10, + AO_HAVE_METHOD_FLAGS_HI = 1 << 11, + AO_HAVE_CODE_FLAGS_HI = 1 << 12, +#define ARCHIVE_BIT_DO(F) \ + F(AO_HAVE_SPECIAL_FORMATS) F(AO_HAVE_CP_NUMBERS) F(AO_HAVE_ALL_CODE_FLAGS) \ + /*F(AO_3_UNUSED_MBZ)*/ \ + F(AO_HAVE_FILE_HEADERS) F(AO_DEFLATE_HINT) F(AO_HAVE_FILE_MODTIME) \ + F(AO_HAVE_FILE_OPTIONS) F(AO_HAVE_FILE_SIZE_HI) F(AO_HAVE_CLASS_FLAGS_HI) \ + F(AO_HAVE_FIELD_FLAGS_HI) F(AO_HAVE_METHOD_FLAGS_HI) F(AO_HAVE_CODE_FLAGS_HI) \ + /*(end)*/ + + // Constants for decoding attribute definition header bytes. + ADH_CONTEXT_MASK = 0x3, // (hdr & ADH_CONTEXT_MASK) + ADH_BIT_SHIFT = 0x2, // (hdr >> ADH_BIT_SHIFT) + ADH_BIT_IS_LSB = 1, // (hdr >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB +#define ADH_BYTE(context, index) ((((index) + ADH_BIT_IS_LSB) << ADH_BIT_SHIFT) + (context)) +#define ADH_BYTE_CONTEXT(adhb) ((adhb) & ADH_CONTEXT_MASK) +#define ADH_BYTE_INDEX(adhb) (((adhb) >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB) + NO_MODTIME = 0, // nullptr modtime value + + // meta-coding + _meta_default = 0, + _meta_canon_min = 1, + _meta_canon_max = 115, + _meta_arb = 116, + _meta_run = 117, + _meta_pop = 141, + _meta_limit = 189, + _meta_error = 255, + _xxx_1_end +}; + +// Bytecodes. + +enum +{ + bc_nop = 0, // 0x00 + bc_aconst_null = 1, // 0x01 + bc_iconst_m1 = 2, // 0x02 + bc_iconst_0 = 3, // 0x03 + bc_iconst_1 = 4, // 0x04 + bc_iconst_2 = 5, // 0x05 + bc_iconst_3 = 6, // 0x06 + bc_iconst_4 = 7, // 0x07 + bc_iconst_5 = 8, // 0x08 + bc_lconst_0 = 9, // 0x09 + bc_lconst_1 = 10, // 0x0a + bc_fconst_0 = 11, // 0x0b + bc_fconst_1 = 12, // 0x0c + bc_fconst_2 = 13, // 0x0d + bc_dconst_0 = 14, // 0x0e + bc_dconst_1 = 15, // 0x0f + bc_bipush = 16, // 0x10 + bc_sipush = 17, // 0x11 + bc_ldc = 18, // 0x12 + bc_ldc_w = 19, // 0x13 + bc_ldc2_w = 20, // 0x14 + bc_iload = 21, // 0x15 + bc_lload = 22, // 0x16 + bc_fload = 23, // 0x17 + bc_dload = 24, // 0x18 + bc_aload = 25, // 0x19 + bc_iload_0 = 26, // 0x1a + bc_iload_1 = 27, // 0x1b + bc_iload_2 = 28, // 0x1c + bc_iload_3 = 29, // 0x1d + bc_lload_0 = 30, // 0x1e + bc_lload_1 = 31, // 0x1f + bc_lload_2 = 32, // 0x20 + bc_lload_3 = 33, // 0x21 + bc_fload_0 = 34, // 0x22 + bc_fload_1 = 35, // 0x23 + bc_fload_2 = 36, // 0x24 + bc_fload_3 = 37, // 0x25 + bc_dload_0 = 38, // 0x26 + bc_dload_1 = 39, // 0x27 + bc_dload_2 = 40, // 0x28 + bc_dload_3 = 41, // 0x29 + bc_aload_0 = 42, // 0x2a + bc_aload_1 = 43, // 0x2b + bc_aload_2 = 44, // 0x2c + bc_aload_3 = 45, // 0x2d + bc_iaload = 46, // 0x2e + bc_laload = 47, // 0x2f + bc_faload = 48, // 0x30 + bc_daload = 49, // 0x31 + bc_aaload = 50, // 0x32 + bc_baload = 51, // 0x33 + bc_caload = 52, // 0x34 + bc_saload = 53, // 0x35 + bc_istore = 54, // 0x36 + bc_lstore = 55, // 0x37 + bc_fstore = 56, // 0x38 + bc_dstore = 57, // 0x39 + bc_astore = 58, // 0x3a + bc_istore_0 = 59, // 0x3b + bc_istore_1 = 60, // 0x3c + bc_istore_2 = 61, // 0x3d + bc_istore_3 = 62, // 0x3e + bc_lstore_0 = 63, // 0x3f + bc_lstore_1 = 64, // 0x40 + bc_lstore_2 = 65, // 0x41 + bc_lstore_3 = 66, // 0x42 + bc_fstore_0 = 67, // 0x43 + bc_fstore_1 = 68, // 0x44 + bc_fstore_2 = 69, // 0x45 + bc_fstore_3 = 70, // 0x46 + bc_dstore_0 = 71, // 0x47 + bc_dstore_1 = 72, // 0x48 + bc_dstore_2 = 73, // 0x49 + bc_dstore_3 = 74, // 0x4a + bc_astore_0 = 75, // 0x4b + bc_astore_1 = 76, // 0x4c + bc_astore_2 = 77, // 0x4d + bc_astore_3 = 78, // 0x4e + bc_iastore = 79, // 0x4f + bc_lastore = 80, // 0x50 + bc_fastore = 81, // 0x51 + bc_dastore = 82, // 0x52 + bc_aastore = 83, // 0x53 + bc_bastore = 84, // 0x54 + bc_castore = 85, // 0x55 + bc_sastore = 86, // 0x56 + bc_pop = 87, // 0x57 + bc_pop2 = 88, // 0x58 + bc_dup = 89, // 0x59 + bc_dup_x1 = 90, // 0x5a + bc_dup_x2 = 91, // 0x5b + bc_dup2 = 92, // 0x5c + bc_dup2_x1 = 93, // 0x5d + bc_dup2_x2 = 94, // 0x5e + bc_swap = 95, // 0x5f + bc_iadd = 96, // 0x60 + bc_ladd = 97, // 0x61 + bc_fadd = 98, // 0x62 + bc_dadd = 99, // 0x63 + bc_isub = 100, // 0x64 + bc_lsub = 101, // 0x65 + bc_fsub = 102, // 0x66 + bc_dsub = 103, // 0x67 + bc_imul = 104, // 0x68 + bc_lmul = 105, // 0x69 + bc_fmul = 106, // 0x6a + bc_dmul = 107, // 0x6b + bc_idiv = 108, // 0x6c + bc_ldiv = 109, // 0x6d + bc_fdiv = 110, // 0x6e + bc_ddiv = 111, // 0x6f + bc_irem = 112, // 0x70 + bc_lrem = 113, // 0x71 + bc_frem = 114, // 0x72 + bc_drem = 115, // 0x73 + bc_ineg = 116, // 0x74 + bc_lneg = 117, // 0x75 + bc_fneg = 118, // 0x76 + bc_dneg = 119, // 0x77 + bc_ishl = 120, // 0x78 + bc_lshl = 121, // 0x79 + bc_ishr = 122, // 0x7a + bc_lshr = 123, // 0x7b + bc_iushr = 124, // 0x7c + bc_lushr = 125, // 0x7d + bc_iand = 126, // 0x7e + bc_land = 127, // 0x7f + bc_ior = 128, // 0x80 + bc_lor = 129, // 0x81 + bc_ixor = 130, // 0x82 + bc_lxor = 131, // 0x83 + bc_iinc = 132, // 0x84 + bc_i2l = 133, // 0x85 + bc_i2f = 134, // 0x86 + bc_i2d = 135, // 0x87 + bc_l2i = 136, // 0x88 + bc_l2f = 137, // 0x89 + bc_l2d = 138, // 0x8a + bc_f2i = 139, // 0x8b + bc_f2l = 140, // 0x8c + bc_f2d = 141, // 0x8d + bc_d2i = 142, // 0x8e + bc_d2l = 143, // 0x8f + bc_d2f = 144, // 0x90 + bc_i2b = 145, // 0x91 + bc_i2c = 146, // 0x92 + bc_i2s = 147, // 0x93 + bc_lcmp = 148, // 0x94 + bc_fcmpl = 149, // 0x95 + bc_fcmpg = 150, // 0x96 + bc_dcmpl = 151, // 0x97 + bc_dcmpg = 152, // 0x98 + bc_ifeq = 153, // 0x99 + bc_ifne = 154, // 0x9a + bc_iflt = 155, // 0x9b + bc_ifge = 156, // 0x9c + bc_ifgt = 157, // 0x9d + bc_ifle = 158, // 0x9e + bc_if_icmpeq = 159, // 0x9f + bc_if_icmpne = 160, // 0xa0 + bc_if_icmplt = 161, // 0xa1 + bc_if_icmpge = 162, // 0xa2 + bc_if_icmpgt = 163, // 0xa3 + bc_if_icmple = 164, // 0xa4 + bc_if_acmpeq = 165, // 0xa5 + bc_if_acmpne = 166, // 0xa6 + bc_goto = 167, // 0xa7 + bc_jsr = 168, // 0xa8 + bc_ret = 169, // 0xa9 + bc_tableswitch = 170, // 0xaa + bc_lookupswitch = 171, // 0xab + bc_ireturn = 172, // 0xac + bc_lreturn = 173, // 0xad + bc_freturn = 174, // 0xae + bc_dreturn = 175, // 0xaf + bc_areturn = 176, // 0xb0 + bc_return = 177, // 0xb1 + bc_getstatic = 178, // 0xb2 + bc_putstatic = 179, // 0xb3 + bc_getfield = 180, // 0xb4 + bc_putfield = 181, // 0xb5 + bc_invokevirtual = 182, // 0xb6 + bc_invokespecial = 183, // 0xb7 + bc_invokestatic = 184, // 0xb8 + bc_invokeinterface = 185, // 0xb9 + bc_xxxunusedxxx = 186, // 0xba + bc_new = 187, // 0xbb + bc_newarray = 188, // 0xbc + bc_anewarray = 189, // 0xbd + bc_arraylength = 190, // 0xbe + bc_athrow = 191, // 0xbf + bc_checkcast = 192, // 0xc0 + bc_instanceof = 193, // 0xc1 + bc_monitorenter = 194, // 0xc2 + bc_monitorexit = 195, // 0xc3 + bc_wide = 196, // 0xc4 + bc_multianewarray = 197, // 0xc5 + bc_ifnull = 198, // 0xc6 + bc_ifnonnull = 199, // 0xc7 + bc_goto_w = 200, // 0xc8 + bc_jsr_w = 201, // 0xc9 + bc_bytecode_limit = 202 // 0xca +}; + +enum +{ + bc_end_marker = 255, + bc_byte_escape = 254, + bc_ref_escape = 253, + _first_linker_op = bc_getstatic, + _last_linker_op = bc_invokestatic, + _num_linker_ops = (_last_linker_op - _first_linker_op) + 1, + _self_linker_op = bc_bytecode_limit, + _self_linker_aload_flag = 1 * _num_linker_ops, + _self_linker_super_flag = 2 * _num_linker_ops, + _self_linker_limit = _self_linker_op + 4 * _num_linker_ops, + _invokeinit_op = _self_linker_limit, + _invokeinit_self_option = 0, + _invokeinit_super_option = 1, + _invokeinit_new_option = 2, + _invokeinit_limit = _invokeinit_op + 3, + _xldc_op = _invokeinit_limit, + bc_aldc = bc_ldc, + bc_cldc = _xldc_op + 0, + bc_ildc = _xldc_op + 1, + bc_fldc = _xldc_op + 2, + bc_aldc_w = bc_ldc_w, + bc_cldc_w = _xldc_op + 3, + bc_ildc_w = _xldc_op + 4, + bc_fldc_w = _xldc_op + 5, + bc_lldc2_w = bc_ldc2_w, + bc_dldc2_w = _xldc_op + 6, + _xldc_limit = _xldc_op + 7, + _xxx_3_end +}; diff --git a/depends/pack200/src/defines.h b/depends/pack200/src/defines.h new file mode 100644 index 00000000..cfe5fc28 --- /dev/null +++ b/depends/pack200/src/defines.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2001, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// random definitions + +#ifdef _MSC_VER +#include <windows.h> +#include <winuser.h> +#else +#include <unistd.h> +#endif + +// Error messages that we have +#define ERROR_ENOMEM "Memory allocation failed" +#define ERROR_FORMAT "Corrupted pack file" +#define ERROR_RESOURCE "Cannot extract resource file" +#define ERROR_OVERFLOW "Internal buffer overflow" +#define ERROR_INTERNAL "Internal error" + +#define lengthof(array) (sizeof(array) / sizeof(array[0])) + +#define NEW(T, n) (T *) must_malloc((int)(scale_size(n, sizeof(T)))) +#define U_NEW(T, n) (T *) u->alloc(scale_size(n, sizeof(T))) +#define T_NEW(T, n) (T *) u->temp_alloc(scale_size(n, sizeof(T))) + +typedef signed char byte; + +#ifdef _MSC_VER +#define MKDIR(dir) mkdir(dir) +#define getpid() _getpid() +#define PATH_MAX MAX_PATH +#define dup2(a, b) _dup2(a, b) +#define strcasecmp(s1, s2) _stricmp(s1, s2) +#define tempname _tempname +#define sleep Sleep +#else +#define MKDIR(dir) mkdir(dir, 0777); +#endif + +/* Must cast to void *, then size_t, then int. */ +#define ptrlowbits(x) ((int)(size_t)(void *)(x)) + +#define DEFAULT_ARCHIVE_MODTIME 1060000000 // Aug 04, 2003 5:26 PM PDT diff --git a/depends/pack200/src/unpack.cpp b/depends/pack200/src/unpack.cpp new file mode 100644 index 00000000..d7de1b22 --- /dev/null +++ b/depends/pack200/src/unpack.cpp @@ -0,0 +1,4806 @@ +/* + * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -*- C++ -*- +// Program for unpacking specially compressed Java packages. +// John R. Rose + +/* + * When compiling for a 64bit LP64 system (longs and pointers being 64bits), + * the printf format %ld is correct and use of %lld will cause warning + * errors from some compilers (gcc/g++). + * _LP64 can be explicitly set (used on Linux). + * Solaris compilers will define __sparcv9 or __x86_64 on 64bit compilations. + */ +#if defined(_LP64) || defined(__sparcv9) || defined(__x86_64) +#define LONG_LONG_FORMAT "%ld" +#define LONG_LONG_HEX_FORMAT "%lx" +#else +#define LONG_LONG_FORMAT "%lld" +#define LONG_LONG_HEX_FORMAT "%016llx" +#endif + +#include <sys/types.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <assert.h> +#include <limits.h> +#include <time.h> +#include <stdint.h> + +#include "defines.h" +#include "bytes.h" +#include "utils.h" +#include "coding.h" +#include "bands.h" + +#include "constants.h" + +#include "zip.h" + +#include "unpack.h" + +// tags, in canonical order: +static const byte TAGS_IN_ORDER[] = { + CONSTANT_Utf8, CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, + CONSTANT_Double, CONSTANT_String, CONSTANT_Class, CONSTANT_Signature, + CONSTANT_NameandType, CONSTANT_Fieldref, CONSTANT_Methodref, CONSTANT_InterfaceMethodref}; +#define N_TAGS_IN_ORDER (sizeof TAGS_IN_ORDER) + +// REQUESTED must be -2 for u2 and REQUESTED_LDC must be -1 for u1 +enum +{ + NOT_REQUESTED = 0, + REQUESTED = -2, + REQUESTED_LDC = -1 +}; + +#define NO_INORD ((uint32_t) - 1) + +struct entry +{ + byte tag; + unsigned short nrefs; // pack w/ tag + + int outputIndex; + uint32_t inord; // &cp.entries[cp.tag_base[this->tag]+this->inord] == this + + entry **refs; + + // put last to pack best + union + { + bytes b; + int i; + int64_t l; + } value; + + void requestOutputIndex(constant_pool &cp, int req = REQUESTED); + int getOutputIndex() + { + assert(outputIndex > NOT_REQUESTED); + return outputIndex; + } + + entry *ref(int refnum) + { + assert((uint32_t)refnum < nrefs); + return refs[refnum]; + } + + const char *utf8String() + { + assert(tagMatches(CONSTANT_Utf8)); + assert(value.b.len == strlen((const char *)value.b.ptr)); + return (const char *)value.b.ptr; + } + + entry *className() + { + assert(tagMatches(CONSTANT_Class)); + return ref(0); + } + + entry *memberClass() + { + assert(tagMatches(CONSTANT_Member)); + return ref(0); + } + + entry *memberDescr() + { + assert(tagMatches(CONSTANT_Member)); + return ref(1); + } + + entry *descrName() + { + assert(tagMatches(CONSTANT_NameandType)); + return ref(0); + } + + entry *descrType() + { + assert(tagMatches(CONSTANT_NameandType)); + return ref(1); + } + + int typeSize(); + + bytes &asUtf8(); + int asInteger() + { + assert(tag == CONSTANT_Integer); + return value.i; + } + + bool isUtf8(bytes &b) + { + return tagMatches(CONSTANT_Utf8) && value.b.equals(b); + } + + bool isDoubleWord() + { + return tag == CONSTANT_Double || tag == CONSTANT_Long; + } + + bool tagMatches(byte tag2) + { + return (tag2 == tag) || (tag2 == CONSTANT_Utf8 && tag == CONSTANT_Signature) || + (tag2 == CONSTANT_Literal && tag >= CONSTANT_Integer && tag <= CONSTANT_String && + tag != CONSTANT_Class) || + (tag2 == CONSTANT_Member && tag >= CONSTANT_Fieldref && + tag <= CONSTANT_InterfaceMethodref); + } +}; + +entry *cpindex::get(uint32_t i) +{ + if (i >= len) + return nullptr; + else if (base1 != nullptr) + // primary index + return &base1[i]; + else + // secondary index + return base2[i]; +} + +inline bytes &entry::asUtf8() +{ + assert(tagMatches(CONSTANT_Utf8)); + return value.b; +} + +int entry::typeSize() +{ + assert(tagMatches(CONSTANT_Utf8)); + const char *sigp = (char *)value.b.ptr; + switch (*sigp) + { + case '(': + sigp++; + break; // skip opening '(' + case 'D': + case 'J': + return 2; // double field + default: + return 1; // field + } + int siglen = 0; + for (;;) + { + int ch = *sigp++; + switch (ch) + { + case 'D': + case 'J': + siglen += 1; + break; + case '[': + // Skip rest of array info. + while (ch == '[') + { + ch = *sigp++; + } + if (ch != 'L') + break; + // else fall through + case 'L': + sigp = strchr(sigp, ';'); + if (sigp == nullptr) + { + unpack_abort("bad data"); + return 0; + } + sigp += 1; + break; + case ')': // closing ')' + return siglen; + } + siglen += 1; + } +} + +inline cpindex *constant_pool::getFieldIndex(entry *classRef) +{ + assert(classRef->tagMatches(CONSTANT_Class)); + assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]); + return &member_indexes[classRef->inord * 2 + 0]; +} +inline cpindex *constant_pool::getMethodIndex(entry *classRef) +{ + assert(classRef->tagMatches(CONSTANT_Class)); + assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]); + return &member_indexes[classRef->inord * 2 + 1]; +} + +struct inner_class +{ + entry *inner; + entry *outer; + entry *name; + int flags; + inner_class *next_sibling; + bool requested; +}; + +// Here is where everything gets deallocated: +void unpacker::free() +{ + int i; + if (jarout != nullptr) + jarout->reset(); + if (gzin != nullptr) + { + gzin->free(); + gzin = nullptr; + } + if (free_input) + input.free(); + /* + * free everybody ever allocated with U_NEW or (recently) with T_NEW + */ + assert(smallbuf.base() == nullptr || mallocs.contains(smallbuf.base())); + assert(tsmallbuf.base() == nullptr || tmallocs.contains(tsmallbuf.base())); + mallocs.freeAll(); + tmallocs.freeAll(); + smallbuf.init(); + tsmallbuf.init(); + bcimap.free(); + class_fixup_type.free(); + class_fixup_offset.free(); + class_fixup_ref.free(); + code_fixup_type.free(); + code_fixup_offset.free(); + code_fixup_source.free(); + requested_ics.free(); + cur_classfile_head.free(); + cur_classfile_tail.free(); + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + attr_defs[i].free(); + + // free CP state + cp.outputEntries.free(); + for (i = 0; i < CONSTANT_Limit; i++) + cp.tag_extras[i].free(); +} + +// input handling +// Attempts to advance rplimit so that (rplimit-rp) is at least 'more'. +// Will eagerly read ahead by larger chunks, if possible. +// Returns false if (rplimit-rp) is not at least 'more', +// unless rplimit hits input.limit(). +bool unpacker::ensure_input(int64_t more) +{ + uint64_t want = more - input_remaining(); + if ((int64_t)want <= 0) + return true; // it's already in the buffer + if (rplimit == input.limit()) + return true; // not expecting any more + + if (read_input_fn == nullptr) + { + // assume it is already all there + bytes_read += input.limit() - rplimit; + rplimit = input.limit(); + return true; + } + + uint64_t remaining = (input.limit() - rplimit); // how much left to read? + byte *rpgoal = (want >= remaining) ? input.limit() : rplimit + (size_t)want; + enum + { + CHUNK_SIZE = (1 << 14) + }; + uint64_t fetch = want; + if (fetch < CHUNK_SIZE) + fetch = CHUNK_SIZE; + if (fetch > remaining * 3 / 4) + fetch = remaining; + // Try to fetch at least "more" bytes. + while ((int64_t)fetch > 0) + { + int64_t nr = (*read_input_fn)(this, rplimit, fetch, remaining); + if (nr <= 0) + { + return (rplimit >= rpgoal); + } + remaining -= nr; + rplimit += nr; + fetch -= nr; + bytes_read += nr; + assert(remaining == (uint64_t)(input.limit() - rplimit)); + } + return true; +} + +// output handling + +fillbytes *unpacker::close_output(fillbytes *which) +{ + assert(wp != nullptr); + if (which == nullptr) + { + if (wpbase == cur_classfile_head.base()) + { + which = &cur_classfile_head; + } + else + { + which = &cur_classfile_tail; + } + } + assert(wpbase == which->base()); + assert(wplimit == which->end()); + which->setLimit(wp); + wp = nullptr; + wplimit = nullptr; + // wpbase = nullptr; + return which; +} + +// maybe_inline +void unpacker::ensure_put_space(size_t size) +{ + if (wp + size <= wplimit) + return; + // Determine which segment needs expanding. + fillbytes *which = close_output(); + byte *wp0 = which->grow(size); + wpbase = which->base(); + wplimit = which->end(); + wp = wp0; +} + +byte *unpacker::put_space(size_t size) +{ + byte *wp0 = wp; + byte *wp1 = wp0 + size; + if (wp1 > wplimit) + { + ensure_put_space(size); + wp0 = wp; + wp1 = wp0 + size; + } + wp = wp1; + return wp0; +} + +void unpacker::putu2_at(byte *wp, int n) +{ + if (n != (unsigned short)n) + { + unpack_abort(ERROR_OVERFLOW); + return; + } + wp[0] = (n) >> 8; + wp[1] = (n) >> 0; +} + +void unpacker::putu4_at(byte *wp, int n) +{ + wp[0] = (n) >> 24; + wp[1] = (n) >> 16; + wp[2] = (n) >> 8; + wp[3] = (n) >> 0; +} + +void unpacker::putu8_at(byte *wp, int64_t n) +{ + putu4_at(wp + 0, (int)((uint64_t)n >> 32)); + putu4_at(wp + 4, (int)((uint64_t)n >> 0)); +} + +void unpacker::putu2(int n) +{ + putu2_at(put_space(2), n); +} + +void unpacker::putu4(int n) +{ + putu4_at(put_space(4), n); +} + +void unpacker::putu8(int64_t n) +{ + putu8_at(put_space(8), n); +} + +int unpacker::putref_index(entry *e, int size) +{ + if (e == nullptr) + return 0; + else if (e->outputIndex > NOT_REQUESTED) + return e->outputIndex; + else if (e->tag == CONSTANT_Signature) + return putref_index(e->ref(0), size); + else + { + e->requestOutputIndex(cp, -size); + // Later on we'll fix the bits. + class_fixup_type.addByte(size); + class_fixup_offset.add((int)wpoffset()); + class_fixup_ref.add(e); + return 0; + } +} + +void unpacker::putref(entry *e) +{ + int oidx = putref_index(e, 2); + putu2_at(put_space(2), oidx); +} + +void unpacker::putu1ref(entry *e) +{ + int oidx = putref_index(e, 1); + putu1_at(put_space(1), oidx); +} + +static int total_cp_size[] = {0, 0}; +static int largest_cp_ref[] = {0, 0}; +static int hash_probes[] = {0, 0}; + +// Allocation of small and large blocks. + +enum +{ + CHUNK = (1 << 14), + SMALL = (1 << 9) +}; + +// Call malloc. Try to combine small blocks and free much later. +void *unpacker::alloc_heap(size_t size, bool smallOK, bool temp) +{ + if (!smallOK || size > SMALL) + { + void *res = must_malloc((int)size); + (temp ? &tmallocs : &mallocs)->add(res); + return res; + } + fillbytes &xsmallbuf = *(temp ? &tsmallbuf : &smallbuf); + if (!xsmallbuf.canAppend(size + 1)) + { + xsmallbuf.init(CHUNK); + (temp ? &tmallocs : &mallocs)->add(xsmallbuf.base()); + } + int growBy = (int)size; + growBy += -growBy & 7; // round up mod 8 + return xsmallbuf.grow(growBy); +} + +void unpacker::saveTo(bytes &b, byte *ptr, size_t len) +{ + b.ptr = U_NEW(byte, add_size(len, 1)); + b.len = len; + b.copyFrom(ptr, len); +} + +// Read up through band_headers. +// Do the archive_size dance to set the size of the input mega-buffer. +void unpacker::read_file_header() +{ + // Read file header to determine file type and total size. + enum + { + MAGIC_BYTES = 4, + AH_LENGTH_0 = 3, // minver, majver, options are outside of archive_size + AH_LENGTH_0_MAX = AH_LENGTH_0 + 1, // options might have 2 bytes + AH_LENGTH = 26, // maximum archive header length (w/ all fields) + // Length contributions from optional header fields: + AH_FILE_HEADER_LEN = 5, // sizehi/lo/next/modtime/files + AH_ARCHIVE_SIZE_LEN = 2, // sizehi/lo only; part of AH_FILE_HEADER_LEN + AH_CP_NUMBER_LEN = 4, // int/float/long/double + AH_SPECIAL_FORMAT_LEN = 2, // layouts/band-headers + AH_LENGTH_MIN = + AH_LENGTH - (AH_FILE_HEADER_LEN + AH_SPECIAL_FORMAT_LEN + AH_CP_NUMBER_LEN), + ARCHIVE_SIZE_MIN = AH_LENGTH_MIN - (AH_LENGTH_0 + AH_ARCHIVE_SIZE_LEN), + FIRST_READ = MAGIC_BYTES + AH_LENGTH_MIN + }; + + assert(AH_LENGTH_MIN == 15); // # of UNSIGNED5 fields required after archive_magic + assert(ARCHIVE_SIZE_MIN == 10); // # of UNSIGNED5 fields required after archive_size + // An absolute minimum nullptr archive is magic[4], {minver,majver,options}[3], + // archive_size[0], cp_counts[8], class_counts[4], for a total of 19 bytes. + // (Note that archive_size is optional; it may be 0..10 bytes in length.) + // The first read must capture everything up through the options field. + // This happens to work even if {minver,majver,options} is a pathological + // 15 bytes long. Legal pack files limit those three fields to 1+1+2 bytes. + assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0 * B_MAX); + + // Up through archive_size, the largest possible archive header is + // magic[4], {minver,majver,options}[4], archive_size[10]. + // (Note only the low 12 bits of options are allowed to be non-zero.) + // In order to parse archive_size, we need at least this many bytes + // in the first read. Of course, if archive_size_hi is more than + // a byte, we probably will fail to allocate the buffer, since it + // will be many gigabytes long. This is a practical, not an + // architectural limit to Pack200 archive sizes. + assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0_MAX + 2 * B_MAX); + + bool foreign_buf = (read_input_fn == nullptr); + byte initbuf[(int)FIRST_READ + (int)C_SLOP + 200]; // 200 is for JAR I/O + if (foreign_buf) + { + // inbytes is all there is + input.set(inbytes); + rp = input.base(); + rplimit = input.limit(); + } + else + { + // inbytes, if not empty, contains some read-ahead we must use first + // ensure_input will take care of copying it into initbuf, + // then querying read_input_fn for any additional data needed. + // However, the caller must assume that we use up all of inbytes. + // There is no way to tell the caller that we used only part of them. + // Therefore, the caller must use only a bare minimum of read-ahead. + if (inbytes.len > FIRST_READ) + { + unpack_abort("too much read-ahead"); + } + input.set(initbuf, sizeof(initbuf)); + input.b.clear(); + input.b.copyFrom(inbytes); + rplimit = rp = input.base(); + rplimit += inbytes.len; + bytes_read += inbytes.len; + } + // Read only 19 bytes, which is certain to contain #archive_options fields, + // but is certain not to overflow past the archive_header. + input.b.len = FIRST_READ; + if (!ensure_input(FIRST_READ)) + unpack_abort("EOF reading archive magic number"); + + if (rp[0] == 'P' && rp[1] == 'K') + { + // In the Unix-style program, we simply simulate a copy command. + // Copy until EOF; assume the JAR file is the last segment. + fprintf(stderr, "Copy-mode.\n"); + for (;;) + { + jarout->write_data(rp, (int)input_remaining()); + if (foreign_buf) + break; // one-time use of a passed in buffer + if (input.size() < CHUNK) + { + // Get some breathing room. + input.set(U_NEW(byte, (size_t)CHUNK + C_SLOP), (size_t)CHUNK); + } + rp = rplimit = input.base(); + if (!ensure_input(1)) + break; + } + jarout->closeJarFile(false); + return; + } + + // Read the magic number. + magic = 0; + for (int i1 = 0; i1 < (int)sizeof(magic); i1++) + { + magic <<= 8; + magic += (*rp++ & 0xFF); + } + + // Read the first 3 values from the header. + value_stream hdr; + int hdrVals = 0; + int hdrValsSkipped = 0; // debug only + hdr.init(rp, rplimit, UNSIGNED5_spec); + minver = hdr.getInt(); + majver = hdr.getInt(); + hdrVals += 2; + + if (magic != (int)JAVA_PACKAGE_MAGIC || + (majver != JAVA5_PACKAGE_MAJOR_VERSION && majver != JAVA6_PACKAGE_MAJOR_VERSION) || + (minver != JAVA5_PACKAGE_MINOR_VERSION && minver != JAVA6_PACKAGE_MINOR_VERSION)) + { + char message[200]; + sprintf(message, "@" ERROR_FORMAT ": magic/ver = " + "%08X/%d.%d should be %08X/%d.%d OR %08X/%d.%d\n", + magic, majver, minver, JAVA_PACKAGE_MAGIC, JAVA5_PACKAGE_MAJOR_VERSION, + JAVA5_PACKAGE_MINOR_VERSION, JAVA_PACKAGE_MAGIC, JAVA6_PACKAGE_MAJOR_VERSION, + JAVA6_PACKAGE_MINOR_VERSION); + unpack_abort(message); + } + + archive_options = hdr.getInt(); + hdrVals += 1; + assert(hdrVals == AH_LENGTH_0); // first three fields only + +#define ORBIT(bit) | (bit) + int OPTION_LIMIT = (0 ARCHIVE_BIT_DO(ORBIT)); +#undef ORBIT + if ((archive_options & ~OPTION_LIMIT) != 0) + { + fprintf(stderr, "Warning: Illegal archive options 0x%x\n", archive_options); + unpack_abort("illegal archive options"); + return; + } + + if ((archive_options & AO_HAVE_FILE_HEADERS) != 0) + { + uint32_t hi = hdr.getInt(); + uint32_t lo = hdr.getInt(); + uint64_t x = band::makeLong(hi, lo); + archive_size = (size_t)x; + if (archive_size != x) + { + // Silly size specified; force overflow. + archive_size = PSIZE_MAX + 1; + } + hdrVals += 2; + } + else + { + hdrValsSkipped += 2; + } + + // Now we can size the whole archive. + // Read everything else into a mega-buffer. + rp = hdr.rp; + int header_size_0 = (int)(rp - input.base()); // used-up header (4byte + 3int) + int header_size_1 = (int)(rplimit - rp); // buffered unused initial fragment + int header_size = header_size_0 + header_size_1; + unsized_bytes_read = header_size_0; + if (foreign_buf) + { + if (archive_size > (size_t)header_size_1) + { + unpack_abort("EOF reading fixed input buffer"); + return; + } + } + else if (archive_size != 0) + { + if (archive_size < ARCHIVE_SIZE_MIN) + { + unpack_abort("impossible archive size"); // bad input data + return; + } + if (archive_size < header_size_1) + { + unpack_abort("too much read-ahead"); // somehow we pre-fetched too much? + return; + } + input.set(U_NEW(byte, add_size(header_size_0, archive_size, C_SLOP)), + (size_t)header_size_0 + archive_size); + assert(input.limit()[0] == 0); + // Move all the bytes we read initially into the real buffer. + input.b.copyFrom(initbuf, header_size); + rp = input.b.ptr + header_size_0; + rplimit = input.b.ptr + header_size; + } + else + { + // It's more complicated and painful. + // A zero archive_size means that we must read until EOF. + input.init(CHUNK * 2); + input.b.len = input.allocated; + rp = rplimit = input.base(); + // Set up input buffer as if we already read the header: + input.b.copyFrom(initbuf, header_size); + rplimit += header_size; + while (ensure_input(input.limit() - rp)) + { + size_t dataSoFar = input_remaining(); + size_t nextSize = add_size(dataSoFar, CHUNK); + input.ensureSize(nextSize); + input.b.len = input.allocated; + rp = rplimit = input.base(); + rplimit += dataSoFar; + } + size_t dataSize = (rplimit - input.base()); + input.b.len = dataSize; + input.grow(C_SLOP); + free_input = true; // free it later + input.b.len = dataSize; + assert(input.limit()[0] == 0); + rp = rplimit = input.base(); + rplimit += dataSize; + rp += header_size_0; // already scanned these bytes... + } + live_input = true; // mark as "do not reuse" + + // read the rest of the header fields + ensure_input((AH_LENGTH - AH_LENGTH_0) * B_MAX); + hdr.rp = rp; + hdr.rplimit = rplimit; + + if ((archive_options & AO_HAVE_FILE_HEADERS) != 0) + { + archive_next_count = hdr.getInt(); + if (archive_next_count < 0) + unpack_abort("bad archive_next_count"); + archive_modtime = hdr.getInt(); + file_count = hdr.getInt(); + if (file_count < 0) + unpack_abort("bad file_count"); + hdrVals += 3; + } + else + { + hdrValsSkipped += 3; + } + + if ((archive_options & AO_HAVE_SPECIAL_FORMATS) != 0) + { + band_headers_size = hdr.getInt(); + if (band_headers_size < 0) + unpack_abort("bad band_headers_size"); + attr_definition_count = hdr.getInt(); + if (attr_definition_count < 0) + unpack_abort("bad attr_definition_count"); + hdrVals += 2; + } + else + { + hdrValsSkipped += 2; + } + + int cp_counts[N_TAGS_IN_ORDER]; + for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) + { + if (!(archive_options & AO_HAVE_CP_NUMBERS)) + { + switch (TAGS_IN_ORDER[k]) + { + case CONSTANT_Integer: + case CONSTANT_Float: + case CONSTANT_Long: + case CONSTANT_Double: + cp_counts[k] = 0; + hdrValsSkipped += 1; + continue; + } + } + cp_counts[k] = hdr.getInt(); + if (cp_counts[k] < 0) + unpack_abort("bad cp_counts"); + hdrVals += 1; + } + + ic_count = hdr.getInt(); + if (ic_count < 0) + unpack_abort("bad ic_count"); + + default_class_minver = hdr.getInt(); + default_class_majver = hdr.getInt(); + + class_count = hdr.getInt(); + if (class_count < 0) + unpack_abort("bad class_count"); + + hdrVals += 4; + + // done with archive_header + hdrVals += hdrValsSkipped; + assert(hdrVals == AH_LENGTH); + + rp = hdr.rp; + if (rp > rplimit) + unpack_abort("EOF reading archive header"); + + // Now size the CP. + cp.init(this, cp_counts); + + default_file_modtime = archive_modtime; + if (default_file_modtime == 0 && !(archive_options & AO_HAVE_FILE_MODTIME)) + default_file_modtime = DEFAULT_ARCHIVE_MODTIME; // taken from driver + if ((archive_options & AO_DEFLATE_HINT) != 0) + default_file_options |= FO_DEFLATE_HINT; + + // meta-bytes, if any, immediately follow archive header + // band_headers.readData(band_headers_size); + ensure_input(band_headers_size); + if (input_remaining() < (size_t)band_headers_size) + { + unpack_abort("EOF reading band headers"); + return; + } + bytes band_headers; + // The "1+" allows an initial byte to be pushed on the front. + band_headers.set(1 + U_NEW(byte, 1 + band_headers_size + C_SLOP), band_headers_size); + + // Start scanning band headers here: + band_headers.copyFrom(rp, band_headers.len); + rp += band_headers.len; + assert(rp <= rplimit); + meta_rp = band_headers.ptr; + // Put evil meta-codes at the end of the band headers, + // so we are sure to throw an error if we run off the end. + bytes::of(band_headers.limit(), C_SLOP).clear(_meta_error); +} + +void unpacker::finish() +{ + if (verbose >= 1) + { + fprintf(stderr, "A total of " LONG_LONG_FORMAT " bytes were read in %d segment(s).\n", + (bytes_read_before_reset + bytes_read), segments_read_before_reset + 1); + fprintf(stderr, "A total of " LONG_LONG_FORMAT " file content bytes were written.\n", + (bytes_written_before_reset + bytes_written)); + fprintf(stderr, + "A total of %d files (of which %d are classes) were written to output.\n", + files_written_before_reset + files_written, + classes_written_before_reset + classes_written); + } + if (jarout != nullptr) + jarout->closeJarFile(true); +} + +// Cf. PackageReader.readConstantPoolCounts +void constant_pool::init(unpacker *u_, int counts[NUM_COUNTS]) +{ + this->u = u_; + + // Fill-pointer for CP. + int next_entry = 0; + + // Size the constant pool: + for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) + { + byte tag = TAGS_IN_ORDER[k]; + int len = counts[k]; + tag_count[tag] = len; + tag_base[tag] = next_entry; + next_entry += len; + // Detect and defend against constant pool size overflow. + // (Pack200 forbids the sum of CP counts to exceed 2^29-1.) + enum + { + CP_SIZE_LIMIT = (1 << 29), + IMPLICIT_ENTRY_COUNT = 1 // empty Utf8 string + }; + if (len >= (1 << 29) || len < 0 || next_entry >= CP_SIZE_LIMIT + IMPLICIT_ENTRY_COUNT) + { + unpack_abort("archive too large: constant pool limit exceeded"); + } + } + + // Close off the end of the CP: + nentries = next_entry; + + // place a limit on future CP growth: + int generous = 0; + generous = add_size(generous, u->ic_count); // implicit name + generous = add_size(generous, u->ic_count); // outer + generous = add_size(generous, u->ic_count); // outer.utf8 + generous = add_size(generous, 40); // WKUs, misc + generous = add_size(generous, u->class_count); // implicit SourceFile strings + maxentries = add_size(nentries, generous); + + // Note that this CP does not include "empty" entries + // for longs and doubles. Those are introduced when + // the entries are renumbered for classfile output. + + entries = U_NEW(entry, maxentries); + + first_extra_entry = &entries[nentries]; + + // Initialize the standard indexes. + tag_count[CONSTANT_All] = nentries; + tag_base[CONSTANT_All] = 0; + for (int tag = 0; tag < CONSTANT_Limit; tag++) + { + entry *cpMap = &entries[tag_base[tag]]; + tag_index[tag].init(tag_count[tag], cpMap, tag); + } + + // Initialize hashTab to a generous power-of-two size. + uint32_t pow2 = 1; + uint32_t target = maxentries + maxentries / 2; // 60% full + while (pow2 < target) + pow2 <<= 1; + hashTab = U_NEW(entry *, hashTabLength = pow2); +} + +static byte *store_Utf8_char(byte *cp, unsigned short ch) +{ + if (ch >= 0x001 && ch <= 0x007F) + { + *cp++ = (byte)ch; + } + else if (ch <= 0x07FF) + { + *cp++ = (byte)(0xC0 | ((ch >> 6) & 0x1F)); + *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F)); + } + else + { + *cp++ = (byte)(0xE0 | ((ch >> 12) & 0x0F)); + *cp++ = (byte)(0x80 | ((ch >> 6) & 0x3F)); + *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F)); + } + return cp; +} + +static byte *skip_Utf8_chars(byte *cp, int len) +{ + for (;; cp++) + { + int ch = *cp & 0xFF; + if ((ch & 0xC0) != 0x80) + { + if (len-- == 0) + return cp; + if (ch < 0x80 && len == 0) + return cp + 1; + } + } +} + +static int compare_Utf8_chars(bytes &b1, bytes &b2) +{ + int l1 = (int)b1.len; + int l2 = (int)b2.len; + int l0 = (l1 < l2) ? l1 : l2; + byte *p1 = b1.ptr; + byte *p2 = b2.ptr; + int c0 = 0; + for (int i = 0; i < l0; i++) + { + int c1 = p1[i] & 0xFF; + int c2 = p2[i] & 0xFF; + if (c1 != c2) + { + // Before returning the obvious answer, + // check to see if c1 or c2 is part of a 0x0000, + // which encodes as {0xC0,0x80}. The 0x0000 is the + // lowest-sorting Java char value, and yet it encodes + // as if it were the first char after 0x7F, which causes + // strings containing nulls to sort too high. All other + // comparisons are consistent between Utf8 and Java chars. + if (c1 == 0xC0 && (p1[i + 1] & 0xFF) == 0x80) + c1 = 0; + if (c2 == 0xC0 && (p2[i + 1] & 0xFF) == 0x80) + c2 = 0; + if (c0 == 0xC0) + { + assert(((c1 | c2) & 0xC0) == 0x80); // c1 & c2 are extension chars + if (c1 == 0x80) + c1 = 0; // will sort below c2 + if (c2 == 0x80) + c2 = 0; // will sort below c1 + } + return c1 - c2; + } + c0 = c1; // save away previous char + } + // common prefix is identical; return length difference if any + return l1 - l2; +} + +// Cf. PackageReader.readUtf8Bands +void unpacker::read_Utf8_values(entry *cpMap, int len) +{ + // Implicit first Utf8 string is the empty string. + enum + { + // certain bands begin with implicit zeroes + PREFIX_SKIP_2 = 2, + SUFFIX_SKIP_1 = 1 + }; + + int i; + + // First band: Read lengths of shared prefixes. + if (len > PREFIX_SKIP_2) + cp_Utf8_prefix.readData(len - PREFIX_SKIP_2); + + // Second band: Read lengths of unshared suffixes: + if (len > SUFFIX_SKIP_1) + cp_Utf8_suffix.readData(len - SUFFIX_SKIP_1); + + bytes *allsuffixes = T_NEW(bytes, len); + + int nbigsuf = 0; + fillbytes charbuf; // buffer to allocate small strings + charbuf.init(); + + // Third band: Read the char values in the unshared suffixes: + cp_Utf8_chars.readData(cp_Utf8_suffix.getIntTotal()); + for (i = 0; i < len; i++) + { + int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt(); + if (suffix < 0) + { + unpack_abort("bad utf8 suffix"); + } + if (suffix == 0 && i >= SUFFIX_SKIP_1) + { + // chars are packed in cp_Utf8_big_chars + nbigsuf += 1; + continue; + } + bytes &chars = allsuffixes[i]; + uint32_t size3 = suffix * 3; // max Utf8 length + bool isMalloc = (suffix > SMALL); + if (isMalloc) + { + chars.malloc(size3); + } + else + { + if (!charbuf.canAppend(size3 + 1)) + { + assert(charbuf.allocated == 0 || tmallocs.contains(charbuf.base())); + charbuf.init(CHUNK); // Reset to new buffer. + tmallocs.add(charbuf.base()); + } + chars.set(charbuf.grow(size3 + 1), size3); + } + + byte *chp = chars.ptr; + for (int j = 0; j < suffix; j++) + { + unsigned short ch = cp_Utf8_chars.getInt(); + chp = store_Utf8_char(chp, ch); + } + // shrink to fit: + if (isMalloc) + { + chars.realloc(chp - chars.ptr); + tmallocs.add(chars.ptr); // free it later + } + else + { + int shrink = (int)(chars.limit() - chp); + chars.len -= shrink; + charbuf.b.len -= shrink; // ungrow to reclaim buffer space + // Note that we did not reclaim the final '\0'. + assert(chars.limit() == charbuf.limit() - 1); + assert(strlen((char *)chars.ptr) == chars.len); + } + } + // cp_Utf8_chars.done(); + + // Fourth band: Go back and size the specially packed strings. + int maxlen = 0; + cp_Utf8_big_suffix.readData(nbigsuf); + cp_Utf8_suffix.rewind(); + for (i = 0; i < len; i++) + { + int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt(); + int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt(); + if (prefix < 0 || prefix + suffix < 0) + { + unpack_abort("bad utf8 prefix"); + } + bytes &chars = allsuffixes[i]; + if (suffix == 0 && i >= SUFFIX_SKIP_1) + { + suffix = cp_Utf8_big_suffix.getInt(); + assert(chars.ptr == nullptr); + chars.len = suffix; // just a momentary hack + } + else + { + assert(chars.ptr != nullptr); + } + if (maxlen < prefix + suffix) + { + maxlen = prefix + suffix; + } + } + // cp_Utf8_suffix.done(); // will use allsuffixes[i].len (ptr!=nullptr) + // cp_Utf8_big_suffix.done(); // will use allsuffixes[i].len + + // Fifth band(s): Get the specially packed characters. + cp_Utf8_big_suffix.rewind(); + for (i = 0; i < len; i++) + { + bytes &chars = allsuffixes[i]; + if (chars.ptr != nullptr) + continue; // already input + int suffix = (int)chars.len; // pick up the hack + uint32_t size3 = suffix * 3; + if (suffix == 0) + continue; // done with empty string + chars.malloc(size3); + byte *chp = chars.ptr; + band saved_band = cp_Utf8_big_chars; + cp_Utf8_big_chars.readData(suffix); + for (int j = 0; j < suffix; j++) + { + unsigned short ch = cp_Utf8_big_chars.getInt(); + chp = store_Utf8_char(chp, ch); + } + chars.realloc(chp - chars.ptr); + tmallocs.add(chars.ptr); // free it later + // cp_Utf8_big_chars.done(); + cp_Utf8_big_chars = saved_band; // reset the band for the next string + } + cp_Utf8_big_chars.readData(0); // zero chars + // cp_Utf8_big_chars.done(); + + // Finally, sew together all the prefixes and suffixes. + bytes bigbuf; + bigbuf.malloc(maxlen * 3 + 1); // max Utf8 length, plus slop for nullptr + int prevlen = 0; // previous string length (in chars) + tmallocs.add(bigbuf.ptr); // free after this block + cp_Utf8_prefix.rewind(); + for (i = 0; i < len; i++) + { + bytes &chars = allsuffixes[i]; + int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt(); + int suffix = (int)chars.len; + byte *fillp; + // by induction, the buffer is already filled with the prefix + // make sure the prefix value is not corrupted, though: + if (prefix > prevlen) + { + unpack_abort("utf8 prefix overflow"); + return; + } + fillp = skip_Utf8_chars(bigbuf.ptr, prefix); + // copy the suffix into the same buffer: + fillp = chars.writeTo(fillp); + assert(bigbuf.inBounds(fillp)); + *fillp = 0; // bigbuf must contain a well-formed Utf8 string + int length = (int)(fillp - bigbuf.ptr); + bytes &value = cpMap[i].value.b; + value.set(U_NEW(byte, add_size(length, 1)), length); + value.copyFrom(bigbuf.ptr, length); + // Index all Utf8 strings + entry *&htref = cp.hashTabRef(CONSTANT_Utf8, value); + if (htref == nullptr) + { + // Note that if two identical strings are transmitted, + // the first is taken to be the canonical one. + htref = &cpMap[i]; + } + prevlen = prefix + suffix; + } + // cp_Utf8_prefix.done(); + + // Free intermediate buffers. + free_temps(); +} + +void unpacker::read_single_words(band &cp_band, entry *cpMap, int len) +{ + cp_band.readData(len); + for (int i = 0; i < len; i++) + { + cpMap[i].value.i = cp_band.getInt(); // coding handles signs OK + } +} + +void unpacker::read_double_words(band &cp_bands, entry *cpMap, int len) +{ + band &cp_band_hi = cp_bands; + band &cp_band_lo = cp_bands.nextBand(); + cp_band_hi.readData(len); + cp_band_lo.readData(len); + for (int i = 0; i < len; i++) + { + cpMap[i].value.l = cp_band_hi.getLong(cp_band_lo, true); + } + // cp_band_hi.done(); + // cp_band_lo.done(); +} + +void unpacker::read_single_refs(band &cp_band, byte refTag, entry *cpMap, int len) +{ + assert(refTag == CONSTANT_Utf8); + cp_band.setIndexByTag(refTag); + cp_band.readData(len); + int indexTag = (cp_band.bn == e_cp_Class) ? CONSTANT_Class : 0; + for (int i = 0; i < len; i++) + { + entry &e = cpMap[i]; + e.refs = U_NEW(entry *, e.nrefs = 1); + entry *utf = cp_band.getRef(); + e.refs[0] = utf; + e.value.b = utf->value.b; // copy value of Utf8 string to self + if (indexTag != 0) + { + // Maintain cross-reference: + entry *&htref = cp.hashTabRef(indexTag, e.value.b); + if (htref == nullptr) + { + // Note that if two identical classes are transmitted, + // the first is taken to be the canonical one. + htref = &e; + } + } + } + // cp_band.done(); +} + +void unpacker::read_double_refs(band &cp_band, byte ref1Tag, byte ref2Tag, entry *cpMap, + int len) +{ + band &cp_band1 = cp_band; + band &cp_band2 = cp_band.nextBand(); + cp_band1.setIndexByTag(ref1Tag); + cp_band2.setIndexByTag(ref2Tag); + cp_band1.readData(len); + cp_band2.readData(len); + for (int i = 0; i < len; i++) + { + entry &e = cpMap[i]; + e.refs = U_NEW(entry *, e.nrefs = 2); + e.refs[0] = cp_band1.getRef(); + e.refs[1] = cp_band2.getRef(); + } + // cp_band1.done(); + // cp_band2.done(); +} + +// Cf. PackageReader.readSignatureBands +void unpacker::read_signature_values(entry *cpMap, int len) +{ + cp_Signature_form.setIndexByTag(CONSTANT_Utf8); + cp_Signature_form.readData(len); + int ncTotal = 0; + int i; + for (i = 0; i < len; i++) + { + entry &e = cpMap[i]; + entry &form = *cp_Signature_form.getRef(); + int nc = 0; + + for (const char *ncp = form.utf8String(); *ncp; ncp++) + { + if (*ncp == 'L') + nc++; + } + + ncTotal += nc; + e.refs = U_NEW(entry *, cpMap[i].nrefs = 1 + nc); + e.refs[0] = &form; + } + // cp_Signature_form.done(); + cp_Signature_classes.setIndexByTag(CONSTANT_Class); + cp_Signature_classes.readData(ncTotal); + for (i = 0; i < len; i++) + { + entry &e = cpMap[i]; + for (int j = 1; j < e.nrefs; j++) + { + e.refs[j] = cp_Signature_classes.getRef(); + } + } + // cp_Signature_classes.done(); +} + +// Cf. PackageReader.readConstantPool +void unpacker::read_cp() +{ + byte *rp0 = rp; + + int i; + + for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++) + { + byte tag = TAGS_IN_ORDER[k]; + int len = cp.tag_count[tag]; + int base = cp.tag_base[tag]; + + entry *cpMap = &cp.entries[base]; + for (i = 0; i < len; i++) + { + cpMap[i].tag = tag; + cpMap[i].inord = i; + } + + switch (tag) + { + case CONSTANT_Utf8: + read_Utf8_values(cpMap, len); + break; + case CONSTANT_Integer: + read_single_words(cp_Int, cpMap, len); + break; + case CONSTANT_Float: + read_single_words(cp_Float, cpMap, len); + break; + case CONSTANT_Long: + read_double_words(cp_Long_hi /*& cp_Long_lo*/, cpMap, len); + break; + case CONSTANT_Double: + read_double_words(cp_Double_hi /*& cp_Double_lo*/, cpMap, len); + break; + case CONSTANT_String: + read_single_refs(cp_String, CONSTANT_Utf8, cpMap, len); + break; + case CONSTANT_Class: + read_single_refs(cp_Class, CONSTANT_Utf8, cpMap, len); + break; + case CONSTANT_Signature: + read_signature_values(cpMap, len); + break; + case CONSTANT_NameandType: + read_double_refs(cp_Descr_name /*& cp_Descr_type*/, CONSTANT_Utf8, + CONSTANT_Signature, cpMap, len); + break; + case CONSTANT_Fieldref: + read_double_refs(cp_Field_class /*& cp_Field_desc*/, CONSTANT_Class, + CONSTANT_NameandType, cpMap, len); + break; + case CONSTANT_Methodref: + read_double_refs(cp_Method_class /*& cp_Method_desc*/, CONSTANT_Class, + CONSTANT_NameandType, cpMap, len); + break; + case CONSTANT_InterfaceMethodref: + read_double_refs(cp_Imethod_class /*& cp_Imethod_desc*/, CONSTANT_Class, + CONSTANT_NameandType, cpMap, len); + break; + default: + assert(false); + break; + } + } + + cp.expandSignatures(); + cp.initMemberIndexes(); + +#define SNAME(n, s) #s "\0" + const char *symNames = (ALL_ATTR_DO(SNAME) "<init>"); +#undef SNAME + + for (int sn = 0; sn < constant_pool::s_LIMIT; sn++) + { + assert(symNames[0] >= '0' && symNames[0] <= 'Z'); // sanity + bytes name; + name.set(symNames); + if (name.len > 0 && name.ptr[0] != '0') + { + cp.sym[sn] = cp.ensureUtf8(name); + } + symNames += name.len + 1; // skip trailing nullptr to next name + } + + band::initIndexes(this); +} + +static band *no_bands[] = {nullptr}; // shared empty body + +inline band &unpacker::attr_definitions::fixed_band(int e_class_xxx) +{ + return u->all_bands[xxx_flags_hi_bn + (e_class_xxx - e_class_flags_hi)]; +} +inline band &unpacker::attr_definitions::xxx_flags_hi() +{ + return fixed_band(e_class_flags_hi); +} +inline band &unpacker::attr_definitions::xxx_flags_lo() +{ + return fixed_band(e_class_flags_lo); +} +inline band &unpacker::attr_definitions::xxx_attr_count() +{ + return fixed_band(e_class_attr_count); +} +inline band &unpacker::attr_definitions::xxx_attr_indexes() +{ + return fixed_band(e_class_attr_indexes); +} +inline band &unpacker::attr_definitions::xxx_attr_calls() +{ + return fixed_band(e_class_attr_calls); +} + +inline unpacker::layout_definition * +unpacker::attr_definitions::defineLayout(int idx, entry *nameEntry, const char *layout) +{ + const char *name = nameEntry->value.b.strval(); + layout_definition *lo = defineLayout(idx, name, layout); + lo->nameEntry = nameEntry; + return lo; +} + +unpacker::layout_definition *unpacker::attr_definitions::defineLayout(int idx, const char *name, + const char *layout) +{ + assert(flag_limit != 0); // must be set up already + if (idx >= 0) + { + // Fixed attr. + if (idx >= (int)flag_limit) + unpack_abort("attribute index too large"); + if (isRedefined(idx)) + unpack_abort("redefined attribute index"); + redef |= ((uint64_t)1 << idx); + } + else + { + idx = flag_limit + overflow_count.length(); + overflow_count.add(0); // make a new counter + } + layout_definition *lo = U_NEW(layout_definition, 1); + lo->idx = idx; + lo->name = name; + lo->layout = layout; + for (int adds = (idx + 1) - layouts.length(); adds > 0; adds--) + { + layouts.add(nullptr); + } + layouts.get(idx) = lo; + return lo; +} + +band **unpacker::attr_definitions::buildBands(unpacker::layout_definition *lo) +{ + int i; + if (lo->elems != nullptr) + return lo->bands(); + if (lo->layout[0] == '\0') + { + lo->elems = no_bands; + } + else + { + // Create bands for this attribute by parsing the layout. + bool hasCallables = lo->hasCallables(); + bands_made = 0x10000; // base number for bands made + const char *lp = lo->layout; + lp = parseLayout(lp, lo->elems, -1); + if (lp[0] != '\0' || band_stack.length() > 0) + { + unpack_abort("garbage at end of layout"); + } + band_stack.popTo(0); + + // Fix up callables to point at their callees. + band **bands = lo->elems; + assert(bands == lo->bands()); + int num_callables = 0; + if (hasCallables) + { + while (bands[num_callables] != nullptr) + { + if (bands[num_callables]->le_kind != EK_CBLE) + { + unpack_abort("garbage mixed with callables"); + break; + } + num_callables += 1; + } + } + for (i = 0; i < calls_to_link.length(); i++) + { + band &call = *(band *)calls_to_link.get(i); + assert(call.le_kind == EK_CALL); + // Determine the callee. + int call_num = call.le_len; + if (call_num < 0 || call_num >= num_callables) + { + unpack_abort("bad call in layout"); + break; + } + band &cble = *bands[call_num]; + // Link the call to it. + call.le_body[0] = &cble; + // Distinguish backward calls and callables: + assert(cble.le_kind == EK_CBLE); + //FIXME: hit this one + //assert(cble.le_len == call_num); + cble.le_back |= call.le_back; + } + calls_to_link.popTo(0); + } + return lo->elems; +} + +/* attribute layout language parser + + attribute_layout: + ( layout_element )* | ( callable )+ + layout_element: + ( integral | replication | union | call | reference ) + + callable: + '[' body ']' + body: + ( layout_element )+ + + integral: + ( unsigned_int | signed_int | bc_index | bc_offset | flag ) + unsigned_int: + uint_type + signed_int: + 'S' uint_type + any_int: + ( unsigned_int | signed_int ) + bc_index: + ( 'P' uint_type | 'PO' uint_type ) + bc_offset: + 'O' any_int + flag: + 'F' uint_type + uint_type: + ( 'B' | 'H' | 'I' | 'V' ) + + replication: + 'N' uint_type '[' body ']' + + union: + 'T' any_int (union_case)* '(' ')' '[' (body)? ']' + union_case: + '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']' + union_case_tag: + ( numeral | numeral '-' numeral ) + call: + '(' numeral ')' + + reference: + reference_type ( 'N' )? uint_type + reference_type: + ( constant_ref | schema_ref | utf8_ref | untyped_ref ) + constant_ref: + ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' ) + schema_ref: + ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' ) + utf8_ref: + 'RU' + untyped_ref: + 'RQ' + + numeral: + '(' ('-')? (digit)+ ')' + digit: + ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ) + +*/ + +const char *unpacker::attr_definitions::parseIntLayout(const char *lp, band *&res, byte le_kind, + bool can_be_signed) +{ + const char *lp0 = lp; + band *b = U_NEW(band, 1); + char le = *lp++; + int spec = UNSIGNED5_spec; + if (le == 'S' && can_be_signed) + { + // Note: This is the last use of sign. There is no 'EF_SIGN'. + spec = SIGNED5_spec; + le = *lp++; + } + else if (le == 'B') + { + spec = BYTE1_spec; // unsigned byte + } + b->init(u, bands_made++, spec); + b->le_kind = le_kind; + int le_len = 0; + switch (le) + { + case 'B': + le_len = 1; + break; + case 'H': + le_len = 2; + break; + case 'I': + le_len = 4; + break; + case 'V': + le_len = 0; + break; + default: + unpack_abort("bad layout element"); + } + b->le_len = le_len; + band_stack.add(b); + res = b; + return lp; +} + +const char *unpacker::attr_definitions::parseNumeral(const char *lp, int &res) +{ + const char *lp0 = lp; + bool sgn = false; + if (*lp == '0') + { + res = 0; + return lp + 1; + } // special case '0' + if (*lp == '-') + { + sgn = true; + lp++; + } + const char *dp = lp; + int con = 0; + while (*dp >= '0' && *dp <= '9') + { + int con0 = con; + con *= 10; + con += (*dp++) - '0'; + if (con <= con0) + { + con = -1; + break; + } // numeral overflow + } + if (lp == dp) + { + unpack_abort("missing numeral in layout"); + } + lp = dp; + if (con < 0 && !(sgn && con == -con)) + { + // (Portability note: Misses the error if int is not 32 bits.) + unpack_abort("numeral overflow"); + } + if (sgn) + con = -con; + res = con; + return lp; +} + +band **unpacker::attr_definitions::popBody(int bs_base) +{ + // Return everything that was pushed, as a nullptr-terminated pointer array. + int bs_limit = band_stack.length(); + if (bs_base == bs_limit) + { + return no_bands; + } + else + { + int nb = bs_limit - bs_base; + band **res = U_NEW(band *, add_size(nb, 1)); + for (int i = 0; i < nb; i++) + { + band *b = (band *)band_stack.get(bs_base + i); + res[i] = b; + } + band_stack.popTo(bs_base); + return res; + } +} + +const char *unpacker::attr_definitions::parseLayout(const char *lp, band **&res, int curCble) +{ + const char *lp0 = lp; + int bs_base = band_stack.length(); + bool top_level = (bs_base == 0); + band *b; + enum + { + can_be_signed = true + }; // optional arg to parseIntLayout + + for (bool done = false; !done;) + { + switch (*lp++) + { + case 'B': + case 'H': + case 'I': + case 'V': // unsigned_int + case 'S': // signed_int + --lp; // reparse + case 'F': + lp = parseIntLayout(lp, b, EK_INT); + break; + case 'P': + { + int le_bci = EK_BCI; + if (*lp == 'O') + { + ++lp; + le_bci = EK_BCID; + } + assert(*lp != 'S'); // no PSH, etc. + lp = parseIntLayout(lp, b, EK_INT); + b->le_bci = le_bci; + if (le_bci == EK_BCI) + b->defc = coding::findBySpec(BCI5_spec); + else + b->defc = coding::findBySpec(BRANCH5_spec); + } + break; + case 'O': + lp = parseIntLayout(lp, b, EK_INT, can_be_signed); + b->le_bci = EK_BCO; + b->defc = coding::findBySpec(BRANCH5_spec); + break; + case 'N': // replication: 'N' uint32_t '[' elem ... ']' + lp = parseIntLayout(lp, b, EK_REPL); + assert(*lp == '['); + ++lp; + lp = parseLayout(lp, b->le_body, curCble); + break; + case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']' + lp = parseIntLayout(lp, b, EK_UN, can_be_signed); + { + int union_base = band_stack.length(); + for (;;) + { // for each case + band &k_case = *U_NEW(band, 1); + band_stack.add(&k_case); + k_case.le_kind = EK_CASE; + k_case.bn = bands_made++; + if (*lp++ != '(') + { + unpack_abort("bad union case"); + return ""; + } + if (*lp++ != ')') + { + --lp; // reparse + // Read some case values. (Use band_stack for temp. storage.) + int case_base = band_stack.length(); + for (;;) + { + int caseval = 0; + lp = parseNumeral(lp, caseval); + band_stack.add((void *)(size_t)caseval); + if (*lp == '-') + { + // new in version 160, allow (1-5) for (1,2,3,4,5) + if (u->majver < JAVA6_PACKAGE_MAJOR_VERSION) + { + unpack_abort( + "bad range in union case label (old archive format)"); + return ""; + } + int caselimit = caseval; + lp++; + lp = parseNumeral(lp, caselimit); + if (caseval >= caselimit || + (uint32_t)(caselimit - caseval) > 0x10000) + { + // Note: 0x10000 is arbitrary implementation restriction. + // We can remove it later if it's important to. + unpack_abort("bad range in union case label"); + } + for (;;) + { + ++caseval; + band_stack.add((void *)(size_t)caseval); + if (caseval == caselimit) + break; + } + } + if (*lp != ',') + break; + lp++; + } + if (*lp++ != ')') + { + unpack_abort("bad case label"); + } + // save away the case labels + int ntags = band_stack.length() - case_base; + int *tags = U_NEW(int, add_size(ntags, 1)); + k_case.le_casetags = tags; + *tags++ = ntags; + for (int i = 0; i < ntags; i++) + { + *tags++ = ptrlowbits(band_stack.get(case_base + i)); + } + band_stack.popTo(case_base); + } + // Got le_casetags. Now grab the body. + assert(*lp == '['); + ++lp; + lp = parseLayout(lp, k_case.le_body, curCble); + if (k_case.le_casetags == nullptr) + break; // done + } + b->le_body = popBody(union_base); + } + break; + case '(': // call: '(' -?NN* ')' + { + band &call = *U_NEW(band, 1); + band_stack.add(&call); + call.le_kind = EK_CALL; + call.bn = bands_made++; + call.le_body = U_NEW(band *, 2); // fill in later + int call_num = 0; + lp = parseNumeral(lp, call_num); + call.le_back = (call_num <= 0); + call_num += curCble; // numeral is self-relative offset + call.le_len = call_num; // use le_len as scratch + calls_to_link.add(&call); + if (*lp++ != ')') + { + unpack_abort("bad call label"); + } + } + break; + case 'K': // reference_type: constant_ref + case 'R': // reference_type: schema_ref + { + int ixTag = CONSTANT_None; + if (lp[-1] == 'K') + { + switch (*lp++) + { + case 'I': + ixTag = CONSTANT_Integer; + break; + case 'J': + ixTag = CONSTANT_Long; + break; + case 'F': + ixTag = CONSTANT_Float; + break; + case 'D': + ixTag = CONSTANT_Double; + break; + case 'S': + ixTag = CONSTANT_String; + break; + case 'Q': + ixTag = CONSTANT_Literal; + break; + } + } + else + { + switch (*lp++) + { + case 'C': + ixTag = CONSTANT_Class; + break; + case 'S': + ixTag = CONSTANT_Signature; + break; + case 'D': + ixTag = CONSTANT_NameandType; + break; + case 'F': + ixTag = CONSTANT_Fieldref; + break; + case 'M': + ixTag = CONSTANT_Methodref; + break; + case 'I': + ixTag = CONSTANT_InterfaceMethodref; + break; + case 'U': + ixTag = CONSTANT_Utf8; + break; // utf8_ref + case 'Q': + ixTag = CONSTANT_All; + break; // untyped_ref + } + } + if (ixTag == CONSTANT_None) + { + unpack_abort("bad reference layout"); + break; + } + bool nullOK = false; + if (*lp == 'N') + { + nullOK = true; + lp++; + } + lp = parseIntLayout(lp, b, EK_REF); + b->defc = coding::findBySpec(UNSIGNED5_spec); + b->initRef(ixTag, nullOK); + } + break; + case '[': + { + // [callable1][callable2]... + if (!top_level) + { + unpack_abort("bad nested callable"); + break; + } + curCble += 1; + band &cble = *U_NEW(band, 1); + band_stack.add(&cble); + cble.le_kind = EK_CBLE; + cble.bn = bands_made++; + lp = parseLayout(lp, cble.le_body, curCble); + } + break; + case ']': + // Hit a closing brace. This ends whatever body we were in. + done = true; + break; + case '\0': + // Hit a nullptr. Also ends the (top-level) body. + --lp; // back up, so caller can see the nullptr also + done = true; + break; + default: + unpack_abort("bad layout"); + } + } + + // Return the accumulated bands: + res = popBody(bs_base); + return lp; +} + +void unpacker::read_attr_defs() +{ + int i; + + // Tell each AD which attrc it is and where its fixed flags are: + attr_defs[ATTR_CONTEXT_CLASS].attrc = ATTR_CONTEXT_CLASS; + attr_defs[ATTR_CONTEXT_CLASS].xxx_flags_hi_bn = e_class_flags_hi; + attr_defs[ATTR_CONTEXT_FIELD].attrc = ATTR_CONTEXT_FIELD; + attr_defs[ATTR_CONTEXT_FIELD].xxx_flags_hi_bn = e_field_flags_hi; + attr_defs[ATTR_CONTEXT_METHOD].attrc = ATTR_CONTEXT_METHOD; + attr_defs[ATTR_CONTEXT_METHOD].xxx_flags_hi_bn = e_method_flags_hi; + attr_defs[ATTR_CONTEXT_CODE].attrc = ATTR_CONTEXT_CODE; + attr_defs[ATTR_CONTEXT_CODE].xxx_flags_hi_bn = e_code_flags_hi; + + // Decide whether bands for the optional high flag words are present. + attr_defs[ATTR_CONTEXT_CLASS] + .setHaveLongFlags((archive_options & AO_HAVE_CLASS_FLAGS_HI) != 0); + attr_defs[ATTR_CONTEXT_FIELD] + .setHaveLongFlags((archive_options & AO_HAVE_FIELD_FLAGS_HI) != 0); + attr_defs[ATTR_CONTEXT_METHOD] + .setHaveLongFlags((archive_options & AO_HAVE_METHOD_FLAGS_HI) != 0); + attr_defs[ATTR_CONTEXT_CODE] + .setHaveLongFlags((archive_options & AO_HAVE_CODE_FLAGS_HI) != 0); + + // Set up built-in attrs. + // (The simple ones are hard-coded. The metadata layouts are not.) + const char *md_layout = ( +// parameter annotations: +#define MDL0 "[NB[(1)]]" + MDL0 +// annotations: +#define MDL1 \ + "[NH[(1)]]" \ + "[RSHNH[RUH(1)]]" + MDL1 + // member_value: + "[TB" + "(66,67,73,83,90)[KIH]" + "(68)[KDH]" + "(70)[KFH]" + "(74)[KJH]" + "(99)[RSH]" + "(101)[RSHRUH]" + "(115)[RUH]" + "(91)[NH[(0)]]" + "(64)[" + // nested annotation: + "RSH" + "NH[RUH(0)]" + "]" + "()[]" + "]"); + + const char *md_layout_P = md_layout; + const char *md_layout_A = md_layout + strlen(MDL0); + const char *md_layout_V = md_layout + strlen(MDL0 MDL1); + assert(0 == strncmp(&md_layout_A[-3], ")]][", 4)); + assert(0 == strncmp(&md_layout_V[-3], ")]][", 4)); + + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + { + attr_definitions &ad = attr_defs[i]; + ad.defineLayout(X_ATTR_RuntimeVisibleAnnotations, "RuntimeVisibleAnnotations", + md_layout_A); + ad.defineLayout(X_ATTR_RuntimeInvisibleAnnotations, "RuntimeInvisibleAnnotations", + md_layout_A); + if (i != ATTR_CONTEXT_METHOD) + continue; + ad.defineLayout(METHOD_ATTR_RuntimeVisibleParameterAnnotations, + "RuntimeVisibleParameterAnnotations", md_layout_P); + ad.defineLayout(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, + "RuntimeInvisibleParameterAnnotations", md_layout_P); + ad.defineLayout(METHOD_ATTR_AnnotationDefault, "AnnotationDefault", md_layout_V); + } + + attr_definition_headers.readData(attr_definition_count); + attr_definition_name.readData(attr_definition_count); + attr_definition_layout.readData(attr_definition_count); + +// Initialize correct predef bits, to distinguish predefs from new defs. +#define ORBIT(n, s) | ((uint64_t)1 << n) + attr_defs[ATTR_CONTEXT_CLASS].predef = (0 X_ATTR_DO(ORBIT) CLASS_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_FIELD].predef = (0 X_ATTR_DO(ORBIT) FIELD_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_METHOD].predef = (0 X_ATTR_DO(ORBIT) METHOD_ATTR_DO(ORBIT)); + attr_defs[ATTR_CONTEXT_CODE].predef = (0 O_ATTR_DO(ORBIT) CODE_ATTR_DO(ORBIT)); +#undef ORBIT + // Clear out the redef bits, folding them back into predef. + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + { + attr_defs[i].predef |= attr_defs[i].redef; + attr_defs[i].redef = 0; + } + + // Now read the transmitted locally defined attrs. + // This will set redef bits again. + for (i = 0; i < attr_definition_count; i++) + { + int header = attr_definition_headers.getByte(); + int attrc = ADH_BYTE_CONTEXT(header); + int idx = ADH_BYTE_INDEX(header); + entry *name = attr_definition_name.getRef(); + entry *layout = attr_definition_layout.getRef(); + attr_defs[attrc].defineLayout(idx, name, layout->value.b.strval()); + } +} + +#define NO_ENTRY_YET ((entry *)-1) + +static bool isDigitString(bytes &x, int beg, int end) +{ + if (beg == end) + return false; // nullptr string + byte *xptr = x.ptr; + for (int i = beg; i < end; i++) + { + char ch = xptr[i]; + if (!(ch >= '0' && ch <= '9')) + return false; + } + return true; +} + +enum +{ // constants for parsing class names + SLASH_MIN = '.', + SLASH_MAX = '/', + DOLLAR_MIN = 0, + DOLLAR_MAX = '-'}; + +static int lastIndexOf(int chmin, int chmax, bytes &x, int pos) +{ + byte *ptr = x.ptr; + for (byte *cp = ptr + pos; --cp >= ptr;) + { + assert(x.inBounds(cp)); + if (*cp >= chmin && *cp <= chmax) + return (int)(cp - ptr); + } + return -1; +} + +inner_class *constant_pool::getIC(entry *inner) +{ + if (inner == nullptr) + return nullptr; + assert(inner->tag == CONSTANT_Class); + if (inner->inord == NO_INORD) + return nullptr; + inner_class *ic = ic_index[inner->inord]; + assert(ic == nullptr || ic->inner == inner); + return ic; +} + +inner_class *constant_pool::getFirstChildIC(entry *outer) +{ + if (outer == nullptr) + return nullptr; + assert(outer->tag == CONSTANT_Class); + if (outer->inord == NO_INORD) + return nullptr; + inner_class *ic = ic_child_index[outer->inord]; + assert(ic == nullptr || ic->outer == outer); + return ic; +} + +inner_class *constant_pool::getNextChildIC(inner_class *child) +{ + inner_class *ic = child->next_sibling; + assert(ic == nullptr || ic->outer == child->outer); + return ic; +} + +void unpacker::read_ics() +{ + int i; + int index_size = cp.tag_count[CONSTANT_Class]; + inner_class **ic_index = U_NEW(inner_class *, index_size); + inner_class **ic_child_index = U_NEW(inner_class *, index_size); + cp.ic_index = ic_index; + cp.ic_child_index = ic_child_index; + ics = U_NEW(inner_class, ic_count); + ic_this_class.readData(ic_count); + ic_flags.readData(ic_count); + // Scan flags to get count of long-form bands. + int long_forms = 0; + for (i = 0; i < ic_count; i++) + { + int flags = ic_flags.getInt(); // may be long form! + if ((flags & ACC_IC_LONG_FORM) != 0) + { + long_forms += 1; + ics[i].name = NO_ENTRY_YET; + } + flags &= ~ACC_IC_LONG_FORM; + entry *inner = ic_this_class.getRef(); + uint32_t inord = inner->inord; + assert(inord < (uint32_t)cp.tag_count[CONSTANT_Class]); + if (ic_index[inord] != nullptr) + { + unpack_abort("identical inner class"); + break; + } + ic_index[inord] = &ics[i]; + ics[i].inner = inner; + ics[i].flags = flags; + assert(cp.getIC(inner) == &ics[i]); + } + // ic_this_class.done(); + // ic_flags.done(); + ic_outer_class.readData(long_forms); + ic_name.readData(long_forms); + for (i = 0; i < ic_count; i++) + { + if (ics[i].name == NO_ENTRY_YET) + { + // Long form. + ics[i].outer = ic_outer_class.getRefN(); + ics[i].name = ic_name.getRefN(); + } + else + { + // Fill in outer and name based on inner. + bytes &n = ics[i].inner->value.b; + bytes pkgOuter; + bytes number; + bytes name; + // Parse n into pkgOuter and name (and number). + int dollar1, dollar2; // pointers to $ in the pattern + // parse n = (<pkg>/)*<outer>($<number>)?($<name>)? + int nlen = (int)n.len; + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, nlen) + 1; + dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, nlen); + if (dollar2 < 0) + { + unpack_abort(); + } + assert(dollar2 >= pkglen); + if (isDigitString(n, dollar2 + 1, nlen)) + { + // n = (<pkg>/)*<outer>$<number> + number = n.slice(dollar2 + 1, nlen); + name.set(nullptr, 0); + dollar1 = dollar2; + } + else if (pkglen < (dollar1 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2 - 1)) && + isDigitString(n, dollar1 + 1, dollar2)) + { + // n = (<pkg>/)*<outer>$<number>$<name> + number = n.slice(dollar1 + 1, dollar2); + name = n.slice(dollar2 + 1, nlen); + } + else + { + // n = (<pkg>/)*<outer>$<name> + dollar1 = dollar2; + number.set(nullptr, 0); + name = n.slice(dollar2 + 1, nlen); + } + if (number.ptr == nullptr) + pkgOuter = n.slice(0, dollar1); + else + pkgOuter.set(nullptr, 0); + + if (pkgOuter.ptr != nullptr) + ics[i].outer = cp.ensureClass(pkgOuter); + + if (name.ptr != nullptr) + ics[i].name = cp.ensureUtf8(name); + } + + // update child/sibling list + if (ics[i].outer != nullptr) + { + uint32_t outord = ics[i].outer->inord; + if (outord != NO_INORD) + { + assert(outord < (uint32_t)cp.tag_count[CONSTANT_Class]); + ics[i].next_sibling = ic_child_index[outord]; + ic_child_index[outord] = &ics[i]; + } + } + } + // ic_outer_class.done(); + // ic_name.done(); +} + +void unpacker::read_classes() +{ + class_this.readData(class_count); + class_super.readData(class_count); + class_interface_count.readData(class_count); + class_interface.readData(class_interface_count.getIntTotal()); + +#if 0 + int i; + // Make a little mark on super-classes. + for (i = 0; i < class_count; i++) { + entry* e = class_super.getRefN(); + if (e != nullptr) e->bits |= entry::EB_SUPER; + } + class_super.rewind(); +#endif + + // Members. + class_field_count.readData(class_count); + class_method_count.readData(class_count); + + int field_count = class_field_count.getIntTotal(); + int method_count = class_method_count.getIntTotal(); + + field_descr.readData(field_count); + read_attrs(ATTR_CONTEXT_FIELD, field_count); + method_descr.readData(method_count); + read_attrs(ATTR_CONTEXT_METHOD, method_count); + read_attrs(ATTR_CONTEXT_CLASS, class_count); + read_code_headers(); +} + +int unpacker::attr_definitions::predefCount(uint32_t idx) +{ + return isPredefined(idx) ? flag_count[idx] : 0; +} + +void unpacker::read_attrs(int attrc, int obj_count) +{ + attr_definitions &ad = attr_defs[attrc]; + assert(ad.attrc == attrc); + + int i, idx, count; + + bool haveLongFlags = ad.haveLongFlags(); + + band &xxx_flags_hi = ad.xxx_flags_hi(); + if (haveLongFlags) + xxx_flags_hi.readData(obj_count); + + band &xxx_flags_lo = ad.xxx_flags_lo(); + xxx_flags_lo.readData(obj_count); + + // pre-scan flags, counting occurrences of each index bit + uint64_t indexMask = ad.flagIndexMask(); // which flag bits are index bits? + for (i = 0; i < obj_count; i++) + { + uint64_t indexBits = xxx_flags_hi.getLong(xxx_flags_lo, haveLongFlags); + if ((indexBits & ~indexMask) > (ushort) - 1) + { + unpack_abort("undefined attribute flag bit"); + return; + } + indexBits &= indexMask; // ignore classfile flag bits + for (idx = 0; indexBits != 0; idx++, indexBits >>= 1) + { + ad.flag_count[idx] += (int)(indexBits & 1); + } + } + // we'll scan these again later for output: + xxx_flags_lo.rewind(); + xxx_flags_hi.rewind(); + + band &xxx_attr_count = ad.xxx_attr_count(); + // There is one count element for each 1<<16 bit set in flags: + xxx_attr_count.readData(ad.predefCount(X_ATTR_OVERFLOW)); + + band &xxx_attr_indexes = ad.xxx_attr_indexes(); + int overflowIndexCount = xxx_attr_count.getIntTotal(); + xxx_attr_indexes.readData(overflowIndexCount); + // pre-scan attr indexes, counting occurrences of each value + for (i = 0; i < overflowIndexCount; i++) + { + idx = xxx_attr_indexes.getInt(); + if (!ad.isIndex(idx)) + { + unpack_abort("attribute index out of bounds"); + return; + } + ad.getCount(idx) += 1; + } + xxx_attr_indexes.rewind(); // we'll scan it again later for output + + // We will need a backward call count for each used backward callable. + int backwardCounts = 0; + for (idx = 0; idx < ad.layouts.length(); idx++) + { + layout_definition *lo = ad.getLayout(idx); + if (lo != nullptr && ad.getCount(idx) != 0) + { + // Build the bands lazily, only when they are used. + band **bands = ad.buildBands(lo); + if (lo->hasCallables()) + { + for (i = 0; bands[i] != nullptr; i++) + { + if (bands[i]->le_back) + { + assert(bands[i]->le_kind == EK_CBLE); + backwardCounts += 1; + } + } + } + } + } + ad.xxx_attr_calls().readData(backwardCounts); + + // Read built-in bands. + // Mostly, these are hand-coded equivalents to readBandData(). + switch (attrc) + { + case ATTR_CONTEXT_CLASS: + + count = ad.predefCount(CLASS_ATTR_SourceFile); + class_SourceFile_RUN.readData(count); + + count = ad.predefCount(CLASS_ATTR_EnclosingMethod); + class_EnclosingMethod_RC.readData(count); + class_EnclosingMethod_RDN.readData(count); + + count = ad.predefCount(X_ATTR_Signature); + class_Signature_RS.readData(count); + + ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); + ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); + + count = ad.predefCount(CLASS_ATTR_InnerClasses); + class_InnerClasses_N.readData(count); + + count = class_InnerClasses_N.getIntTotal(); + class_InnerClasses_RC.readData(count); + class_InnerClasses_F.readData(count); + + // Drop remaining columns wherever flags are zero: + count -= class_InnerClasses_F.getIntCount(0); + class_InnerClasses_outer_RCN.readData(count); + class_InnerClasses_name_RUN.readData(count); + + count = ad.predefCount(CLASS_ATTR_ClassFile_version); + class_ClassFile_version_minor_H.readData(count); + class_ClassFile_version_major_H.readData(count); + break; + + case ATTR_CONTEXT_FIELD: + + count = ad.predefCount(FIELD_ATTR_ConstantValue); + field_ConstantValue_KQ.readData(count); + + count = ad.predefCount(X_ATTR_Signature); + field_Signature_RS.readData(count); + + ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); + ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); + break; + + case ATTR_CONTEXT_METHOD: + + code_count = ad.predefCount(METHOD_ATTR_Code); + // Code attrs are handled very specially below... + + count = ad.predefCount(METHOD_ATTR_Exceptions); + method_Exceptions_N.readData(count); + count = method_Exceptions_N.getIntTotal(); + method_Exceptions_RC.readData(count); + + count = ad.predefCount(X_ATTR_Signature); + method_Signature_RS.readData(count); + + ad.readBandData(X_ATTR_RuntimeVisibleAnnotations); + ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations); + ad.readBandData(METHOD_ATTR_RuntimeVisibleParameterAnnotations); + ad.readBandData(METHOD_ATTR_RuntimeInvisibleParameterAnnotations); + ad.readBandData(METHOD_ATTR_AnnotationDefault); + break; + + case ATTR_CONTEXT_CODE: + // (keep this code aligned with its brother in unpacker::write_attrs) + count = ad.predefCount(CODE_ATTR_StackMapTable); + // disable this feature in old archives! + if (count != 0 && majver < JAVA6_PACKAGE_MAJOR_VERSION) + { + unpack_abort("undefined StackMapTable attribute (old archive format)"); + return; + } + code_StackMapTable_N.readData(count); + count = code_StackMapTable_N.getIntTotal(); + code_StackMapTable_frame_T.readData(count); + // the rest of it depends in a complicated way on frame tags + { + int fat_frame_count = 0; + int offset_count = 0; + int type_count = 0; + for (int k = 0; k < count; k++) + { + int tag = code_StackMapTable_frame_T.getByte(); + if (tag <= 127) + { + // (64-127) [(2)] + if (tag >= 64) + type_count++; + } + else if (tag <= 251) + { + // (247) [(1)(2)] + // (248-251) [(1)] + if (tag >= 247) + offset_count++; + if (tag == 247) + type_count++; + } + else if (tag <= 254) + { + // (252) [(1)(2)] + // (253) [(1)(2)(2)] + // (254) [(1)(2)(2)(2)] + offset_count++; + type_count += (tag - 251); + } + else + { + // (255) [(1)NH[(2)]NH[(2)]] + fat_frame_count++; + } + } + + // done pre-scanning frame tags: + code_StackMapTable_frame_T.rewind(); + + // deal completely with fat frames: + offset_count += fat_frame_count; + code_StackMapTable_local_N.readData(fat_frame_count); + type_count += code_StackMapTable_local_N.getIntTotal(); + code_StackMapTable_stack_N.readData(fat_frame_count); + type_count += code_StackMapTable_stack_N.getIntTotal(); + // read the rest: + code_StackMapTable_offset.readData(offset_count); + code_StackMapTable_T.readData(type_count); + // (7) [RCH] + count = code_StackMapTable_T.getIntCount(7); + code_StackMapTable_RC.readData(count); + // (8) [PH] + count = code_StackMapTable_T.getIntCount(8); + code_StackMapTable_P.readData(count); + } + + count = ad.predefCount(CODE_ATTR_LineNumberTable); + code_LineNumberTable_N.readData(count); + count = code_LineNumberTable_N.getIntTotal(); + code_LineNumberTable_bci_P.readData(count); + code_LineNumberTable_line.readData(count); + + count = ad.predefCount(CODE_ATTR_LocalVariableTable); + code_LocalVariableTable_N.readData(count); + count = code_LocalVariableTable_N.getIntTotal(); + code_LocalVariableTable_bci_P.readData(count); + code_LocalVariableTable_span_O.readData(count); + code_LocalVariableTable_name_RU.readData(count); + code_LocalVariableTable_type_RS.readData(count); + code_LocalVariableTable_slot.readData(count); + + count = ad.predefCount(CODE_ATTR_LocalVariableTypeTable); + code_LocalVariableTypeTable_N.readData(count); + count = code_LocalVariableTypeTable_N.getIntTotal(); + code_LocalVariableTypeTable_bci_P.readData(count); + code_LocalVariableTypeTable_span_O.readData(count); + code_LocalVariableTypeTable_name_RU.readData(count); + code_LocalVariableTypeTable_type_RS.readData(count); + code_LocalVariableTypeTable_slot.readData(count); + break; + } + + // Read compressor-defined bands. + for (idx = 0; idx < ad.layouts.length(); idx++) + { + if (ad.getLayout(idx) == nullptr) + continue; // none at this fixed index <32 + if (idx < (int)ad.flag_limit && ad.isPredefined(idx)) + continue; // already handled + if (ad.getCount(idx) == 0) + continue; // no attributes of this type (then why transmit layouts?) + ad.readBandData(idx); + } +} + +void unpacker::attr_definitions::readBandData(int idx) +{ + int j; + uint32_t count = getCount(idx); + if (count == 0) + return; + layout_definition *lo = getLayout(idx); + bool hasCallables = lo->hasCallables(); + band **bands = lo->bands(); + if (!hasCallables) + { + // Read through the rest of the bands in a regular way. + readBandData(bands, count); + } + else + { + // Deal with the callables. + // First set up the forward entry count for each callable. + // This is stored on band::length of the callable. + bands[0]->expectMoreLength(count); + for (j = 0; bands[j] != nullptr; j++) + { + band &j_cble = *bands[j]; + assert(j_cble.le_kind == EK_CBLE); + if (j_cble.le_back) + { + // Add in the predicted effects of backward calls, too. + int back_calls = xxx_attr_calls().getInt(); + j_cble.expectMoreLength(back_calls); + // In a moment, more forward calls may increment j_cble.length. + } + } + // Now consult whichever callables have non-zero entry counts. + readBandData(bands, (uint32_t) - 1); + } +} + +// Recursive helper to the previous function: +void unpacker::attr_definitions::readBandData(band **body, uint32_t count) +{ + int j, k; + for (j = 0; body[j] != nullptr; j++) + { + band &b = *body[j]; + if (b.defc != nullptr) + { + // It has data, so read it. + b.readData(count); + } + switch (b.le_kind) + { + case EK_REPL: + { + int reps = b.getIntTotal(); + readBandData(b.le_body, reps); + } + break; + case EK_UN: + { + int remaining = count; + for (k = 0; b.le_body[k] != nullptr; k++) + { + band &k_case = *b.le_body[k]; + int k_count = 0; + if (k_case.le_casetags == nullptr) + { + k_count = remaining; // last (empty) case + } + else + { + int *tags = k_case.le_casetags; + int ntags = *tags++; // 1st element is length (why not?) + while (ntags-- > 0) + { + int tag = *tags++; + k_count += b.getIntCount(tag); + } + } + readBandData(k_case.le_body, k_count); + remaining -= k_count; + } + assert(remaining == 0); + } + break; + case EK_CALL: + // Push the count forward, if it is not a backward call. + if (!b.le_back) + { + band &cble = *b.le_body[0]; + assert(cble.le_kind == EK_CBLE); + cble.expectMoreLength(count); + } + break; + case EK_CBLE: + assert((int)count == -1); // incoming count is meaningless + k = b.length; + assert(k >= 0); + // This is intended and required for non production mode. + assert((b.length = -1)); // make it unable to accept more calls now. + readBandData(b.le_body, k); + break; + } + } +} + +static inline band **findMatchingCase(int matchTag, band **cases) +{ + for (int k = 0; cases[k] != nullptr; k++) + { + band &k_case = *cases[k]; + if (k_case.le_casetags != nullptr) + { + // If it has tags, it must match a tag. + int *tags = k_case.le_casetags; + int ntags = *tags++; // 1st element is length + for (; ntags > 0; ntags--) + { + int tag = *tags++; + if (tag == matchTag) + break; + } + if (ntags == 0) + continue; // does not match + } + return k_case.le_body; + } + return nullptr; +} + +// write attribute band data: +void unpacker::putlayout(band **body) +{ + int i; + int prevBII = -1; + int prevBCI = -1; + if (body == NULL) + { + unpack_abort("putlayout: unexpected NULL for body"); + return; + } + for (i = 0; body[i] != nullptr; i++) + { + band &b = *body[i]; + byte le_kind = b.le_kind; + + // Handle scalar part, if any. + int x = 0; + entry *e = nullptr; + if (b.defc != nullptr) + { + // It has data, so unparse an element. + if (b.ixTag != CONSTANT_None) + { + assert(le_kind == EK_REF); + if (b.ixTag == CONSTANT_Literal) + e = b.getRefUsing(cp.getKQIndex()); + else + e = b.getRefN(); + switch (b.le_len) + { + case 0: + break; + case 1: + putu1ref(e); + break; + case 2: + putref(e); + break; + case 4: + putu2(0); + putref(e); + break; + default: + assert(false); + } + } + else + { + assert(le_kind == EK_INT || le_kind == EK_REPL || le_kind == EK_UN); + x = b.getInt(); + + assert(!b.le_bci || prevBCI == (int)to_bci(prevBII)); + switch (b.le_bci) + { + case EK_BCI: // PH: transmit R(bci), store bci + x = to_bci(prevBII = x); + prevBCI = x; + break; + case EK_BCID: // POH: transmit D(R(bci)), store bci + x = to_bci(prevBII += x); + prevBCI = x; + break; + case EK_BCO: // OH: transmit D(R(bci)), store D(bci) + x = to_bci(prevBII += x) - prevBCI; + prevBCI += x; + break; + } + assert(!b.le_bci || prevBCI == (int)to_bci(prevBII)); + + switch (b.le_len) + { + case 0: + break; + case 1: + putu1(x); + break; + case 2: + putu2(x); + break; + case 4: + putu4(x); + break; + default: + assert(false); + } + } + } + + // Handle subparts, if any. + switch (le_kind) + { + case EK_REPL: + // x is the repeat count + while (x-- > 0) + { + putlayout(b.le_body); + } + break; + case EK_UN: + // x is the tag + putlayout(findMatchingCase(x, b.le_body)); + break; + case EK_CALL: + { + band &cble = *b.le_body[0]; + assert(cble.le_kind == EK_CBLE); + //FIXME: hit this one + //assert(cble.le_len == b.le_len); + putlayout(cble.le_body); + } + break; + + case EK_CBLE: + case EK_CASE: + assert(false); // should not reach here + } + } +} + +void unpacker::read_files() +{ + file_name.readData(file_count); + if ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0) + file_size_hi.readData(file_count); + file_size_lo.readData(file_count); + if ((archive_options & AO_HAVE_FILE_MODTIME) != 0) + file_modtime.readData(file_count); + int allFiles = file_count + class_count; + if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0) + { + file_options.readData(file_count); + // FO_IS_CLASS_STUB might be set, causing overlap between classes and files + for (int i = 0; i < file_count; i++) + { + if ((file_options.getInt() & FO_IS_CLASS_STUB) != 0) + { + allFiles -= 1; // this one counts as both class and file + } + } + file_options.rewind(); + } + assert((default_file_options & FO_IS_CLASS_STUB) == 0); + files_remaining = allFiles; +} + +void unpacker::get_code_header(int &max_stack, int &max_na_locals, int &handler_count, + int &cflags) +{ + int sc = code_headers.getByte(); + if (sc == 0) + { + max_stack = max_na_locals = handler_count = cflags = -1; + return; + } + // Short code header is the usual case: + int nh; + int mod; + if (sc < 1 + 12 * 12) + { + sc -= 1; + nh = 0; + mod = 12; + } + else if (sc < 1 + 12 * 12 + 8 * 8) + { + sc -= 1 + 12 * 12; + nh = 1; + mod = 8; + } + else + { + assert(sc < 1 + 12 * 12 + 8 * 8 + 7 * 7); + sc -= 1 + 12 * 12 + 8 * 8; + nh = 2; + mod = 7; + } + max_stack = sc % mod; + max_na_locals = sc / mod; // caller must add static, siglen + handler_count = nh; + if ((archive_options & AO_HAVE_ALL_CODE_FLAGS) != 0) + cflags = -1; + else + cflags = 0; // this one has no attributes +} + +// Cf. PackageReader.readCodeHeaders +void unpacker::read_code_headers() +{ + code_headers.readData(code_count); + int totalHandlerCount = 0; + int totalFlagsCount = 0; + for (int i = 0; i < code_count; i++) + { + int max_stack, max_locals, handler_count, cflags; + get_code_header(max_stack, max_locals, handler_count, cflags); + if (max_stack < 0) + code_max_stack.expectMoreLength(1); + if (max_locals < 0) + code_max_na_locals.expectMoreLength(1); + if (handler_count < 0) + code_handler_count.expectMoreLength(1); + else + totalHandlerCount += handler_count; + if (cflags < 0) + totalFlagsCount += 1; + } + code_headers.rewind(); // replay later during writing + + code_max_stack.readData(); + code_max_na_locals.readData(); + code_handler_count.readData(); + totalHandlerCount += code_handler_count.getIntTotal(); + + // Read handler specifications. + // Cf. PackageReader.readCodeHandlers. + code_handler_start_P.readData(totalHandlerCount); + code_handler_end_PO.readData(totalHandlerCount); + code_handler_catch_PO.readData(totalHandlerCount); + code_handler_class_RCN.readData(totalHandlerCount); + + read_attrs(ATTR_CONTEXT_CODE, totalFlagsCount); +} + +static inline bool is_in_range(uint32_t n, uint32_t min, uint32_t max) +{ + return n - min <= max - min; // unsigned arithmetic! +} +static inline bool is_field_op(int bc) +{ + return is_in_range(bc, bc_getstatic, bc_putfield); +} +static inline bool is_invoke_init_op(int bc) +{ + return is_in_range(bc, _invokeinit_op, _invokeinit_limit - 1); +} +static inline bool is_self_linker_op(int bc) +{ + return is_in_range(bc, _self_linker_op, _self_linker_limit - 1); +} +static bool is_branch_op(int bc) +{ + return is_in_range(bc, bc_ifeq, bc_jsr) || is_in_range(bc, bc_ifnull, bc_jsr_w); +} +static bool is_local_slot_op(int bc) +{ + return is_in_range(bc, bc_iload, bc_aload) || is_in_range(bc, bc_istore, bc_astore) || + bc == bc_iinc || bc == bc_ret; +} +band *unpacker::ref_band_for_op(int bc) +{ + switch (bc) + { + case bc_ildc: + case bc_ildc_w: + return &bc_intref; + case bc_fldc: + case bc_fldc_w: + return &bc_floatref; + case bc_lldc2_w: + return &bc_longref; + case bc_dldc2_w: + return &bc_doubleref; + case bc_aldc: + case bc_aldc_w: + return &bc_stringref; + case bc_cldc: + case bc_cldc_w: + return &bc_classref; + + case bc_getstatic: + case bc_putstatic: + case bc_getfield: + case bc_putfield: + return &bc_fieldref; + + case bc_invokevirtual: + case bc_invokespecial: + case bc_invokestatic: + return &bc_methodref; + case bc_invokeinterface: + return &bc_imethodref; + + case bc_new: + case bc_anewarray: + case bc_checkcast: + case bc_instanceof: + case bc_multianewarray: + return &bc_classref; + } + return nullptr; +} + +band *unpacker::ref_band_for_self_op(int bc, bool &isAloadVar, int &origBCVar) +{ + if (!is_self_linker_op(bc)) + return nullptr; + int idx = (bc - _self_linker_op); + bool isSuper = (idx >= _self_linker_super_flag); + if (isSuper) + idx -= _self_linker_super_flag; + bool isAload = (idx >= _self_linker_aload_flag); + if (isAload) + idx -= _self_linker_aload_flag; + int origBC = _first_linker_op + idx; + bool isField = is_field_op(origBC); + isAloadVar = isAload; + origBCVar = _first_linker_op + idx; + if (!isSuper) + return isField ? &bc_thisfield : &bc_thismethod; + else + return isField ? &bc_superfield : &bc_supermethod; +} + +// Cf. PackageReader.readByteCodes +inline // called exactly once => inline + void +unpacker::read_bcs() +{ + // read from bc_codes and bc_case_count + fillbytes all_switch_ops; + all_switch_ops.init(); + + // Read directly from rp/rplimit. + // Do this later: bc_codes.readData(...) + byte *rp0 = rp; + + band *bc_which; + byte *opptr = rp; + byte *oplimit = rplimit; + + bool isAload; // passed by ref and then ignored + int junkBC; // passed by ref and then ignored + for (int k = 0; k < code_count; k++) + { + // Scan one method: + for (;;) + { + if (opptr + 2 > oplimit) + { + rp = opptr; + ensure_input(2); + oplimit = rplimit; + rp = rp0; // back up + } + if (opptr == oplimit) + { + unpack_abort(); + } + int bc = *opptr++ & 0xFF; + bool isWide = false; + if (bc == bc_wide) + { + if (opptr == oplimit) + { + unpack_abort(); + } + bc = *opptr++ & 0xFF; + isWide = true; + } + // Adjust expectations of various band sizes. + switch (bc) + { + case bc_tableswitch: + case bc_lookupswitch: + all_switch_ops.addByte(bc); + break; + case bc_iinc: + bc_local.expectMoreLength(1); + bc_which = isWide ? &bc_short : &bc_byte; + bc_which->expectMoreLength(1); + break; + case bc_sipush: + bc_short.expectMoreLength(1); + break; + case bc_bipush: + bc_byte.expectMoreLength(1); + break; + case bc_newarray: + bc_byte.expectMoreLength(1); + break; + case bc_multianewarray: + assert(ref_band_for_op(bc) == &bc_classref); + bc_classref.expectMoreLength(1); + bc_byte.expectMoreLength(1); + break; + case bc_ref_escape: + bc_escrefsize.expectMoreLength(1); + bc_escref.expectMoreLength(1); + break; + case bc_byte_escape: + bc_escsize.expectMoreLength(1); + // bc_escbyte will have to be counted too + break; + default: + if (is_invoke_init_op(bc)) + { + bc_initref.expectMoreLength(1); + break; + } + bc_which = ref_band_for_self_op(bc, isAload, junkBC); + if (bc_which != nullptr) + { + bc_which->expectMoreLength(1); + break; + } + if (is_branch_op(bc)) + { + bc_label.expectMoreLength(1); + break; + } + bc_which = ref_band_for_op(bc); + if (bc_which != nullptr) + { + bc_which->expectMoreLength(1); + assert(bc != bc_multianewarray); // handled elsewhere + break; + } + if (is_local_slot_op(bc)) + { + bc_local.expectMoreLength(1); + break; + } + break; + case bc_end_marker: + // Increment k and test against code_count. + goto doneScanningMethod; + } + } + doneScanningMethod: + { + } + } + + // Go through the formality, so we can use it in a regular fashion later: + assert(rp == rp0); + bc_codes.readData((int)(opptr - rp)); + + int i = 0; + + // To size instruction bands correctly, we need info on switches: + bc_case_count.readData((int)all_switch_ops.size()); + for (i = 0; i < (int)all_switch_ops.size(); i++) + { + int caseCount = bc_case_count.getInt(); + int bc = all_switch_ops.getByte(i); + bc_label.expectMoreLength(1 + caseCount); // default label + cases + bc_case_value.expectMoreLength(bc == bc_tableswitch ? 1 : caseCount); + } + bc_case_count.rewind(); // uses again for output + + all_switch_ops.free(); + + for (i = e_bc_case_value; i <= e_bc_escsize; i++) + { + all_bands[i].readData(); + } + + // The bc_escbyte band is counted by the immediately previous band. + bc_escbyte.readData(bc_escsize.getIntTotal()); +} + +void unpacker::read_bands() +{ + byte *rp0 = rp; + + read_file_header(); + + if (cp.nentries == 0) + { + // read_file_header failed to read a CP, because it copied a JAR. + return; + } + + // Do this after the file header has been read: + check_options(); + + read_cp(); + read_attr_defs(); + read_ics(); + read_classes(); + read_bcs(); + read_files(); +} + +/// CP routines + +entry *&constant_pool::hashTabRef(byte tag, bytes &b) +{ + uint32_t hash = tag + (int)b.len; + for (int i = 0; i < (int)b.len; i++) + { + hash = hash * 31 + (0xFF & b.ptr[i]); + } + entry **ht = hashTab; + int hlen = hashTabLength; + assert((hlen & (hlen - 1)) == 0); // must be power of 2 + uint32_t hash1 = hash & (hlen - 1); // == hash % hlen + uint32_t hash2 = 0; // lazily computed (requires mod op.) + int probes = 0; + while (ht[hash1] != nullptr) + { + entry &e = *ht[hash1]; + if (e.value.b.equals(b) && e.tag == tag) + break; + if (hash2 == 0) + // Note: hash2 must be relatively prime to hlen, hence the "|1". + hash2 = (((hash % 499) & (hlen - 1)) | 1); + hash1 += hash2; + if (hash1 >= (uint32_t)hlen) + hash1 -= hlen; + assert(hash1 < (uint32_t)hlen); + assert(++probes < hlen); + } + return ht[hash1]; +} + +static void insert_extra(entry *e, ptrlist &extras) +{ + // This ordering helps implement the Pack200 requirement + // of a predictable CP order in the class files produced. + e->inord = NO_INORD; // mark as an "extra" + extras.add(e); + // Note: We will sort the list (by string-name) later. +} + +entry *constant_pool::ensureUtf8(bytes &b) +{ + entry *&ix = hashTabRef(CONSTANT_Utf8, b); + if (ix != nullptr) + return ix; + // Make one. + if (nentries == maxentries) + { + unpack_abort("cp utf8 overflow"); + return &entries[tag_base[CONSTANT_Utf8]]; // return something + } + entry &e = entries[nentries++]; + e.tag = CONSTANT_Utf8; + u->saveTo(e.value.b, b); + assert(&e >= first_extra_entry); + insert_extra(&e, tag_extras[CONSTANT_Utf8]); + return ix = &e; +} + +entry *constant_pool::ensureClass(bytes &b) +{ + entry *&ix = hashTabRef(CONSTANT_Class, b); + if (ix != nullptr) + return ix; + // Make one. + if (nentries == maxentries) + { + unpack_abort("cp class overflow"); + return &entries[tag_base[CONSTANT_Class]]; // return something + } + entry &e = entries[nentries++]; + e.tag = CONSTANT_Class; + e.nrefs = 1; + e.refs = U_NEW(entry *, 1); + ix = &e; // hold my spot in the index + entry *utf = ensureUtf8(b); + e.refs[0] = utf; + e.value.b = utf->value.b; + assert(&e >= first_extra_entry); + insert_extra(&e, tag_extras[CONSTANT_Class]); + return &e; +} + +void constant_pool::expandSignatures() +{ + int i; + int nsigs = 0; + int nreused = 0; + int first_sig = tag_base[CONSTANT_Signature]; + int sig_limit = tag_count[CONSTANT_Signature] + first_sig; + fillbytes buf; + buf.init(1 << 10); + for (i = first_sig; i < sig_limit; i++) + { + entry &e = entries[i]; + assert(e.tag == CONSTANT_Signature); + int refnum = 0; + bytes form = e.refs[refnum++]->asUtf8(); + buf.empty(); + for (int j = 0; j < (int)form.len; j++) + { + int c = form.ptr[j]; + buf.addByte(c); + if (c == 'L') + { + entry *cls = e.refs[refnum++]; + buf.append(cls->className()->asUtf8()); + } + } + assert(refnum == e.nrefs); + bytes &sig = buf.b; + + // try to find a pre-existing Utf8: + entry *&e2 = hashTabRef(CONSTANT_Utf8, sig); + if (e2 != nullptr) + { + assert(e2->isUtf8(sig)); + e.value.b = e2->value.b; + e.refs[0] = e2; + e.nrefs = 1; + nreused++; + } + else + { + // there is no other replacement; reuse this CP entry as a Utf8 + u->saveTo(e.value.b, sig); + e.tag = CONSTANT_Utf8; + e.nrefs = 0; + e2 = &e; + } + nsigs++; + } + buf.free(); + + // go expunge all references to remaining signatures: + for (i = 0; i < (int)nentries; i++) + { + entry &e = entries[i]; + for (int j = 0; j < e.nrefs; j++) + { + entry *&e2 = e.refs[j]; + if (e2 != nullptr && e2->tag == CONSTANT_Signature) + e2 = e2->refs[0]; + } + } +} + +void constant_pool::initMemberIndexes() +{ + // This function does NOT refer to any class schema. + // It is totally internal to the cpool. + int i, j; + + // Get the pre-existing indexes: + int nclasses = tag_count[CONSTANT_Class]; + entry *classes = tag_base[CONSTANT_Class] + entries; + int nfields = tag_count[CONSTANT_Fieldref]; + entry *fields = tag_base[CONSTANT_Fieldref] + entries; + int nmethods = tag_count[CONSTANT_Methodref]; + entry *methods = tag_base[CONSTANT_Methodref] + entries; + + int *field_counts = T_NEW(int, nclasses); + int *method_counts = T_NEW(int, nclasses); + cpindex *all_indexes = U_NEW(cpindex, nclasses * 2); + entry **field_ix = U_NEW(entry *, add_size(nfields, nclasses)); + entry **method_ix = U_NEW(entry *, add_size(nmethods, nclasses)); + + for (j = 0; j < nfields; j++) + { + entry &f = fields[j]; + i = f.memberClass()->inord; + assert(i < nclasses); + field_counts[i]++; + } + for (j = 0; j < nmethods; j++) + { + entry &m = methods[j]; + i = m.memberClass()->inord; + assert(i < nclasses); + method_counts[i]++; + } + + int fbase = 0, mbase = 0; + for (i = 0; i < nclasses; i++) + { + int fc = field_counts[i]; + int mc = method_counts[i]; + all_indexes[i * 2 + 0].init(fc, field_ix + fbase, CONSTANT_Fieldref + SUBINDEX_BIT); + all_indexes[i * 2 + 1].init(mc, method_ix + mbase, CONSTANT_Methodref + SUBINDEX_BIT); + // reuse field_counts and member_counts as fill pointers: + field_counts[i] = fbase; + method_counts[i] = mbase; + fbase += fc + 1; + mbase += mc + 1; + // (the +1 leaves a space between every subarray) + } + assert(fbase == nfields + nclasses); + assert(mbase == nmethods + nclasses); + + for (j = 0; j < nfields; j++) + { + entry &f = fields[j]; + i = f.memberClass()->inord; + field_ix[field_counts[i]++] = &f; + } + for (j = 0; j < nmethods; j++) + { + entry &m = methods[j]; + i = m.memberClass()->inord; + method_ix[method_counts[i]++] = &m; + } + + member_indexes = all_indexes; + + // Free intermediate buffers. + u->free_temps(); +} + +void entry::requestOutputIndex(constant_pool &cp, int req) +{ + assert(outputIndex <= NOT_REQUESTED); // must not have assigned indexes yet + if (tag == CONSTANT_Signature) + { + ref(0)->requestOutputIndex(cp, req); + return; + } + assert(req == REQUESTED || req == REQUESTED_LDC); + if (outputIndex != NOT_REQUESTED) + { + if (req == REQUESTED_LDC) + outputIndex = req; // this kind has precedence + return; + } + outputIndex = req; + // assert(!cp.outputEntries.contains(this)); + assert(tag != CONSTANT_Signature); + cp.outputEntries.add(this); + for (int j = 0; j < nrefs; j++) + { + ref(j)->requestOutputIndex(cp); + } +} + +void constant_pool::resetOutputIndexes() +{ + int i; + int noes = outputEntries.length(); + entry **oes = (entry **)outputEntries.base(); + for (i = 0; i < noes; i++) + { + entry &e = *oes[i]; + e.outputIndex = NOT_REQUESTED; + } + outputIndexLimit = 0; + outputEntries.empty(); +} + +static const byte TAG_ORDER[CONSTANT_Limit] = {0, 1, 0, 2, 3, 4, 5, 7, 6, 10, 11, 12, 9, 8}; + +extern "C" int outputEntry_cmp(const void *e1p, const void *e2p) +{ + // Sort entries according to the Pack200 rules for deterministic + // constant pool ordering. + // + // The four sort keys as follows, in order of decreasing importance: + // 1. ldc first, then non-ldc guys + // 2. normal cp_All entries by input order (i.e., address order) + // 3. after that, extra entries by lexical order (as in tag_extras[*]) + entry &e1 = *(entry *)*(void **)e1p; + entry &e2 = *(entry *)*(void **)e2p; + int oi1 = e1.outputIndex; + int oi2 = e2.outputIndex; + assert(oi1 == REQUESTED || oi1 == REQUESTED_LDC); + assert(oi2 == REQUESTED || oi2 == REQUESTED_LDC); + if (oi1 != oi2) + { + if (oi1 == REQUESTED_LDC) + return 0 - 1; + if (oi2 == REQUESTED_LDC) + return 1 - 0; + // Else fall through; neither is an ldc request. + } + if (e1.inord != NO_INORD || e2.inord != NO_INORD) + { + // One or both is normal. Use input order. + if (&e1 > &e2) + return 1 - 0; + if (&e1 < &e2) + return 0 - 1; + return 0; // equal pointers + } + // Both are extras. Sort by tag and then by value. + if (e1.tag != e2.tag) + { + return TAG_ORDER[e1.tag] - TAG_ORDER[e2.tag]; + } + // If the tags are the same, use string comparison. + return compare_Utf8_chars(e1.value.b, e2.value.b); +} + +void constant_pool::computeOutputIndexes() +{ + int i; + + int noes = outputEntries.length(); + entry **oes = (entry **)outputEntries.base(); + + // Sort the output constant pool into the order required by Pack200. + PTRLIST_QSORT(outputEntries, outputEntry_cmp); + + // Allocate a new index for each entry that needs one. + // We do this in two passes, one for LDC entries and one for the rest. + int nextIndex = 1; // always skip index #0 in output cpool + for (i = 0; i < noes; i++) + { + entry &e = *oes[i]; + assert(e.outputIndex == REQUESTED || e.outputIndex == REQUESTED_LDC); + e.outputIndex = nextIndex++; + if (e.isDoubleWord()) + nextIndex++; // do not use the next index + } + outputIndexLimit = nextIndex; +} + +// Unpacker Start +// Deallocate all internal storage and reset to a clean state. +// Do not disturb any input or output connections, including +// infileptr, inbytes, read_input_fn, jarout, or errstrm. +// Do not reset any unpack options. +void unpacker::reset() +{ + bytes_read_before_reset += bytes_read; + bytes_written_before_reset += bytes_written; + files_written_before_reset += files_written; + classes_written_before_reset += classes_written; + segments_read_before_reset += 1; + if (verbose >= 2) + { + fprintf(stderr, "After segment %d, " LONG_LONG_FORMAT + " bytes read and " LONG_LONG_FORMAT " bytes written.\n", + segments_read_before_reset - 1, bytes_read_before_reset, + bytes_written_before_reset); + fprintf(stderr, + "After segment %d, %d files (of which %d are classes) written to output.\n", + segments_read_before_reset - 1, files_written_before_reset, + classes_written_before_reset); + if (archive_next_count != 0) + { + fprintf(stderr, "After segment %d, %d segment%s remaining (estimated).\n", + segments_read_before_reset - 1, archive_next_count, + archive_next_count == 1 ? "" : "s"); + } + } + + unpacker save_u = (*this); // save bytewise image + infileptr = nullptr; // make asserts happy + jarout = nullptr; // do not close the output jar + gzin = nullptr; // do not close the input gzip stream + this->free(); + this->init(read_input_fn); + + // restore selected interface state: + infileptr = save_u.infileptr; + inbytes = save_u.inbytes; + jarout = save_u.jarout; + gzin = save_u.gzin; + verbose = save_u.verbose; + deflate_hint_or_zero = save_u.deflate_hint_or_zero; + modification_time_or_zero = save_u.modification_time_or_zero; + bytes_read_before_reset = save_u.bytes_read_before_reset; + bytes_written_before_reset = save_u.bytes_written_before_reset; + files_written_before_reset = save_u.files_written_before_reset; + classes_written_before_reset = save_u.classes_written_before_reset; + segments_read_before_reset = save_u.segments_read_before_reset; + // Note: If we use strip_names, watch out: They get nuked here. +} + +void unpacker::init(read_input_fn_t input_fn) +{ + int i; + BYTES_OF(*this).clear(); + this->u = this; // self-reference for U_NEW macro + read_input_fn = input_fn; + all_bands = band::makeBands(this); + // Make a default jar buffer; caller may safely overwrite it. + jarout = U_NEW(jar, 1); + jarout->init(this); + for (i = 0; i < ATTR_CONTEXT_LIMIT; i++) + attr_defs[i].u = u; // set up outer ptr +} + +// Usage: unpack a byte buffer +// packptr is a reference to byte buffer containing a +// packed file and len is the length of the buffer. +// If nullptr, the callback is used to fill an internal buffer. +void unpacker::start(void *packptr, size_t len) +{ + if (packptr != nullptr && len != 0) + { + inbytes.set((byte *)packptr, len); + } + read_bands(); +} + +void unpacker::check_options() +{ + const char *strue = "true"; + const char *sfalse = "false"; + if (deflate_hint_or_zero != 0) + { + bool force_deflate_hint = (deflate_hint_or_zero > 0); + if (force_deflate_hint) + default_file_options |= FO_DEFLATE_HINT; + else + default_file_options &= ~FO_DEFLATE_HINT; + // Turn off per-file deflate hint by force. + suppress_file_options |= FO_DEFLATE_HINT; + } + if (modification_time_or_zero != 0) + { + default_file_modtime = modification_time_or_zero; + // Turn off per-file modtime by force. + archive_options &= ~AO_HAVE_FILE_MODTIME; + } +} + +// classfile writing + +void unpacker::reset_cur_classfile() +{ + // set defaults + cur_class_minver = default_class_minver; + cur_class_majver = default_class_majver; + + // reset constant pool state + cp.resetOutputIndexes(); + + // reset fixups + class_fixup_type.empty(); + class_fixup_offset.empty(); + class_fixup_ref.empty(); + requested_ics.empty(); +} + +cpindex *constant_pool::getKQIndex() +{ + char ch = '?'; + if (u->cur_descr != nullptr) + { + entry *type = u->cur_descr->descrType(); + ch = type->value.b.ptr[0]; + } + byte tag = CONSTANT_Integer; + switch (ch) + { + case 'L': + tag = CONSTANT_String; + break; + case 'I': + tag = CONSTANT_Integer; + break; + case 'J': + tag = CONSTANT_Long; + break; + case 'F': + tag = CONSTANT_Float; + break; + case 'D': + tag = CONSTANT_Double; + break; + case 'B': + case 'S': + case 'C': + case 'Z': + tag = CONSTANT_Integer; + break; + default: + unpack_abort("bad KQ reference"); + break; + } + return getIndex(tag); +} + +uint32_t unpacker::to_bci(uint32_t bii) +{ + uint32_t len = bcimap.length(); + uint32_t *map = (uint32_t *)bcimap.base(); + assert(len > 0); // must be initialized before using to_bci + if (bii < len) + return map[bii]; + // Else it's a fractional or out-of-range BCI. + uint32_t key = bii - len; + for (int i = len;; i--) + { + if (map[i - 1] - (i - 1) <= key) + break; + else + --bii; + } + return bii; +} + +void unpacker::put_stackmap_type() +{ + int tag = code_StackMapTable_T.getByte(); + putu1(tag); + switch (tag) + { + case 7: // (7) [RCH] + putref(code_StackMapTable_RC.getRef()); + break; + case 8: // (8) [PH] + putu2(to_bci(code_StackMapTable_P.getInt())); + break; + } +} + +// Functions for writing code. + +void unpacker::put_label(int curIP, int size) +{ + code_fixup_type.addByte(size); + code_fixup_offset.add((int)put_empty(size)); + code_fixup_source.add(curIP); +} + +inline // called exactly once => inline + void +unpacker::write_bc_ops() +{ + bcimap.empty(); + code_fixup_type.empty(); + code_fixup_offset.empty(); + code_fixup_source.empty(); + + band *bc_which; + + byte *opptr = bc_codes.curRP(); + // No need for oplimit, since the codes are pre-counted. + + size_t codeBase = wpoffset(); + + bool isAload; // copy-out result + int origBC; + + entry *thisClass = cur_class; + entry *superClass = cur_super; + entry *newClass = nullptr; // class of last _new opcode + + // overwrite any prior index on these bands; it changes w/ current class: + bc_thisfield.setIndex(cp.getFieldIndex(thisClass)); + bc_thismethod.setIndex(cp.getMethodIndex(thisClass)); + if (superClass != nullptr) + { + bc_superfield.setIndex(cp.getFieldIndex(superClass)); + bc_supermethod.setIndex(cp.getMethodIndex(superClass)); + } + + for (int curIP = 0;; curIP++) + { + int curPC = (int)(wpoffset() - codeBase); + bcimap.add(curPC); + ensure_put_space(10); // covers most instrs w/o further bounds check + int bc = *opptr++ & 0xFF; + + putu1_fast(bc); + // Note: See '--wp' below for pseudo-bytecodes like bc_end_marker. + + bool isWide = false; + if (bc == bc_wide) + { + bc = *opptr++ & 0xFF; + putu1_fast(bc); + isWide = true; + } + switch (bc) + { + case bc_end_marker: + --wp; // not really part of the code + assert(opptr <= bc_codes.maxRP()); + bc_codes.curRP() = opptr; // advance over this in bc_codes + goto doneScanningMethod; + case bc_tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) + case bc_lookupswitch: // apc: (df, nc, nc*(case, label)) + { + int caseCount = bc_case_count.getInt(); + while (((wpoffset() - codeBase) % 4) != 0) + putu1_fast(0); + ensure_put_space(30 + caseCount * 8); + put_label(curIP, 4); // int df = bc_label.getInt(); + if (bc == bc_tableswitch) + { + int lo = bc_case_value.getInt(); + int hi = lo + caseCount - 1; + putu4(lo); + putu4(hi); + for (int j = 0; j < caseCount; j++) + { + put_label(curIP, 4); // int lVal = bc_label.getInt(); + // int cVal = lo + j; + } + } + else + { + putu4(caseCount); + for (int j = 0; j < caseCount; j++) + { + int cVal = bc_case_value.getInt(); + putu4(cVal); + put_label(curIP, 4); // int lVal = bc_label.getInt(); + } + } + assert((int)to_bci(curIP) == curPC); + continue; + } + case bc_iinc: + { + int local = bc_local.getInt(); + int delta = (isWide ? bc_short : bc_byte).getInt(); + if (isWide) + { + putu2(local); + putu2(delta); + } + else + { + putu1_fast(local); + putu1_fast(delta); + } + continue; + } + case bc_sipush: + { + int val = bc_short.getInt(); + putu2(val); + continue; + } + case bc_bipush: + case bc_newarray: + { + int val = bc_byte.getByte(); + putu1_fast(val); + continue; + } + case bc_ref_escape: + { + // Note that insnMap has one entry for this. + --wp; // not really part of the code + int size = bc_escrefsize.getInt(); + entry *ref = bc_escref.getRefN(); + switch (size) + { + case 1: + putu1ref(ref); + break; + case 2: + putref(ref); + break; + default: + assert(false); + } + continue; + } + case bc_byte_escape: + { + // Note that insnMap has one entry for all these bytes. + --wp; // not really part of the code + int size = bc_escsize.getInt(); + ensure_put_space(size); + for (int j = 0; j < size; j++) + putu1_fast(bc_escbyte.getByte()); + continue; + } + default: + if (is_invoke_init_op(bc)) + { + origBC = bc_invokespecial; + entry *classRef; + switch (bc - _invokeinit_op) + { + case _invokeinit_self_option: + classRef = thisClass; + break; + case _invokeinit_super_option: + classRef = superClass; + break; + default: + assert(bc == _invokeinit_op + _invokeinit_new_option); + case _invokeinit_new_option: + classRef = newClass; + break; + } + wp[-1] = origBC; // overwrite with origBC + int coding = bc_initref.getInt(); + // Find the nth overloading of <init> in classRef. + entry *ref = nullptr; + cpindex *ix = (classRef == nullptr) ? nullptr : cp.getMethodIndex(classRef); + for (int j = 0, which_init = 0;; j++) + { + ref = (ix == nullptr) ? nullptr : ix->get(j); + if (ref == nullptr) + break; // oops, bad input + assert(ref->tag == CONSTANT_Methodref); + if (ref->memberDescr()->descrName() == cp.sym[constant_pool::s_lt_init_gt]) + { + if (which_init++ == coding) + break; + } + } + putref(ref); + continue; + } + bc_which = ref_band_for_self_op(bc, isAload, origBC); + if (bc_which != nullptr) + { + if (!isAload) + { + wp[-1] = origBC; // overwrite with origBC + } + else + { + wp[-1] = bc_aload_0; // overwrite with _aload_0 + // Note: insnMap keeps the _aload_0 separate. + bcimap.add(++curPC); + ++curIP; + putu1_fast(origBC); + } + entry *ref = bc_which->getRef(); + putref(ref); + continue; + } + if (is_branch_op(bc)) + { + // int lVal = bc_label.getInt(); + if (bc < bc_goto_w) + { + put_label(curIP, 2); // putu2(lVal & 0xFFFF); + } + else + { + assert(bc <= bc_jsr_w); + put_label(curIP, 4); // putu4(lVal); + } + assert((int)to_bci(curIP) == curPC); + continue; + } + bc_which = ref_band_for_op(bc); + if (bc_which != nullptr) + { + entry *ref = bc_which->getRefCommon(bc_which->ix, bc_which->nullOK); + if (ref == nullptr && bc_which == &bc_classref) + { + // Shorthand for class self-references. + ref = thisClass; + } + origBC = bc; + switch (bc) + { + case bc_ildc: + case bc_cldc: + case bc_fldc: + case bc_aldc: + origBC = bc_ldc; + break; + case bc_ildc_w: + case bc_cldc_w: + case bc_fldc_w: + case bc_aldc_w: + origBC = bc_ldc_w; + break; + case bc_lldc2_w: + case bc_dldc2_w: + origBC = bc_ldc2_w; + break; + case bc_new: + newClass = ref; + break; + } + wp[-1] = origBC; // overwrite with origBC + if (origBC == bc_ldc) + { + putu1ref(ref); + } + else + { + putref(ref); + } + if (origBC == bc_multianewarray) + { + // Copy the trailing byte also. + int val = bc_byte.getByte(); + putu1_fast(val); + } + else if (origBC == bc_invokeinterface) + { + int argSize = ref->memberDescr()->descrType()->typeSize(); + putu1_fast(1 + argSize); + putu1_fast(0); + } + continue; + } + if (is_local_slot_op(bc)) + { + int local = bc_local.getInt(); + if (isWide) + { + putu2(local); + if (bc == bc_iinc) + { + int iVal = bc_short.getInt(); + putu2(iVal); + } + } + else + { + putu1_fast(local); + if (bc == bc_iinc) + { + int iVal = bc_byte.getByte(); + putu1_fast(iVal); + } + } + continue; + } + // Random bytecode. Just copy it. + assert(bc < bc_bytecode_limit); + } + } +doneScanningMethod: +{ +} + // bcimap.add(curPC); // PC limit is already also in map, from bc_end_marker + + // Armed with a bcimap, we can now fix up all the labels. + for (int i = 0; i < (int)code_fixup_type.size(); i++) + { + int type = code_fixup_type.getByte(i); + byte *bp = wp_at(code_fixup_offset.get(i)); + int curIP = code_fixup_source.get(i); + int destIP = curIP + bc_label.getInt(); + int span = to_bci(destIP) - to_bci(curIP); + switch (type) + { + case 2: + putu2_at(bp, (ushort)span); + break; + case 4: + putu4_at(bp, span); + break; + default: + assert(false); + } + } +} + +inline // called exactly once => inline + void +unpacker::write_code() +{ + int j; + + int max_stack, max_locals, handler_count, cflags; + get_code_header(max_stack, max_locals, handler_count, cflags); + + if (max_stack < 0) + max_stack = code_max_stack.getInt(); + if (max_locals < 0) + max_locals = code_max_na_locals.getInt(); + if (handler_count < 0) + handler_count = code_handler_count.getInt(); + + int siglen = cur_descr->descrType()->typeSize(); + if ((cur_descr_flags & ACC_STATIC) == 0) + siglen++; + max_locals += siglen; + + putu2(max_stack); + putu2(max_locals); + size_t bcbase = put_empty(4); + + // Write the bytecodes themselves. + write_bc_ops(); + + byte *bcbasewp = wp_at(bcbase); + putu4_at(bcbasewp, (int)(wp - (bcbasewp + 4))); // size of code attr + + putu2(handler_count); + for (j = 0; j < handler_count; j++) + { + int bii = code_handler_start_P.getInt(); + putu2(to_bci(bii)); + bii += code_handler_end_PO.getInt(); + putu2(to_bci(bii)); + bii += code_handler_catch_PO.getInt(); + putu2(to_bci(bii)); + putref(code_handler_class_RCN.getRefN()); + } + + uint64_t indexBits = cflags; + if (cflags < 0) + { + bool haveLongFlags = attr_defs[ATTR_CONTEXT_CODE].haveLongFlags(); + indexBits = code_flags_hi.getLong(code_flags_lo, haveLongFlags); + } + write_attrs(ATTR_CONTEXT_CODE, indexBits); +} + +int unpacker::write_attrs(int attrc, uint64_t indexBits) +{ + if (indexBits == 0) + { + // Quick short-circuit. + putu2(0); + return 0; + } + + attr_definitions &ad = attr_defs[attrc]; + + int i, j, j2, idx, count; + + int oiCount = 0; + if (ad.isPredefined(X_ATTR_OVERFLOW) && (indexBits & ((uint64_t)1 << X_ATTR_OVERFLOW)) != 0) + { + indexBits -= ((uint64_t)1 << X_ATTR_OVERFLOW); + oiCount = ad.xxx_attr_count().getInt(); + } + + int bitIndexes[X_ATTR_LIMIT_FLAGS_HI]; + int biCount = 0; + + // Fill bitIndexes with index bits, in order. + for (idx = 0; indexBits != 0; idx++, indexBits >>= 1) + { + if ((indexBits & 1) != 0) + bitIndexes[biCount++] = idx; + } + assert(biCount <= (int)lengthof(bitIndexes)); + + // Write a provisional attribute count, perhaps to be corrected later. + int naOffset = (int)wpoffset(); + int na0 = biCount + oiCount; + putu2(na0); + + int na = 0; + for (i = 0; i < na0; i++) + { + if (i < biCount) + idx = bitIndexes[i]; + else + idx = ad.xxx_attr_indexes().getInt(); + assert(ad.isIndex(idx)); + entry *aname = nullptr; + entry *ref; // scratch + size_t abase = put_empty(2 + 4); + if (idx < (int)ad.flag_limit && ad.isPredefined(idx)) + { + // Switch on the attrc and idx simultaneously. + switch (ADH_BYTE(attrc, idx)) + { + + case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_OVERFLOW) : + case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_OVERFLOW) : + case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_OVERFLOW) : + case ADH_BYTE(ATTR_CONTEXT_CODE, X_ATTR_OVERFLOW) : + // no attribute at all, so back up on this one + wp = wp_at(abase); + continue; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_ClassFile_version) : + cur_class_minver = class_ClassFile_version_minor_H.getInt(); + cur_class_majver = class_ClassFile_version_major_H.getInt(); + // back up; not a real attribute + wp = wp_at(abase); + continue; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_InnerClasses) : + // note the existence of this attr, but save for later + if (cur_class_has_local_ics) + unpack_abort("too many InnerClasses attrs"); + cur_class_has_local_ics = true; + wp = wp_at(abase); + continue; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_SourceFile) : + aname = cp.sym[constant_pool::s_SourceFile]; + ref = class_SourceFile_RUN.getRefN(); + if (ref == nullptr) + { + bytes &n = cur_class->ref(0)->value.b; + // parse n = (<pkg>/)*<outer>?($<id>)* + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, (int)n.len) + 1; + bytes prefix = n.slice(pkglen, n.len); + for (;;) + { + // Work backwards, finding all '$', '#', etc. + int dollar = + lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, prefix, (int)prefix.len); + if (dollar < 0) + break; + prefix = prefix.slice(0, dollar); + } + const char *suffix = ".java"; + int len = (int)(prefix.len + strlen(suffix)); + bytes name; + name.set(T_NEW(byte, add_size(len, 1)), len); + name.strcat(prefix).strcat(suffix); + ref = cp.ensureUtf8(name); + } + putref(ref); + break; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_EnclosingMethod) : + aname = cp.sym[constant_pool::s_EnclosingMethod]; + putref(class_EnclosingMethod_RC.getRefN()); + putref(class_EnclosingMethod_RDN.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_FIELD, FIELD_ATTR_ConstantValue) : + aname = cp.sym[constant_pool::s_ConstantValue]; + putref(field_ConstantValue_KQ.getRefUsing(cp.getKQIndex())); + break; + + case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Code) : + aname = cp.sym[constant_pool::s_Code]; + write_code(); + break; + + case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Exceptions) : + aname = cp.sym[constant_pool::s_Exceptions]; + putu2(count = method_Exceptions_N.getInt()); + for (j = 0; j < count; j++) + { + putref(method_Exceptions_RC.getRefN()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_StackMapTable) : + aname = cp.sym[constant_pool::s_StackMapTable]; + // (keep this code aligned with its brother in unpacker::read_attrs) + putu2(count = code_StackMapTable_N.getInt()); + for (j = 0; j < count; j++) + { + int tag = code_StackMapTable_frame_T.getByte(); + putu1(tag); + if (tag <= 127) + { + // (64-127) [(2)] + if (tag >= 64) + put_stackmap_type(); + } + else if (tag <= 251) + { + // (247) [(1)(2)] + // (248-251) [(1)] + if (tag >= 247) + putu2(code_StackMapTable_offset.getInt()); + if (tag == 247) + put_stackmap_type(); + } + else if (tag <= 254) + { + // (252) [(1)(2)] + // (253) [(1)(2)(2)] + // (254) [(1)(2)(2)(2)] + putu2(code_StackMapTable_offset.getInt()); + for (int k = (tag - 251); k > 0; k--) + { + put_stackmap_type(); + } + } + else + { + // (255) [(1)NH[(2)]NH[(2)]] + putu2(code_StackMapTable_offset.getInt()); + putu2(j2 = code_StackMapTable_local_N.getInt()); + while (j2-- > 0) + put_stackmap_type(); + putu2(j2 = code_StackMapTable_stack_N.getInt()); + while (j2-- > 0) + put_stackmap_type(); + } + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LineNumberTable) : + aname = cp.sym[constant_pool::s_LineNumberTable]; + putu2(count = code_LineNumberTable_N.getInt()); + for (j = 0; j < count; j++) + { + putu2(to_bci(code_LineNumberTable_bci_P.getInt())); + putu2(code_LineNumberTable_line.getInt()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTable) : + aname = cp.sym[constant_pool::s_LocalVariableTable]; + putu2(count = code_LocalVariableTable_N.getInt()); + for (j = 0; j < count; j++) + { + int bii = code_LocalVariableTable_bci_P.getInt(); + int bci = to_bci(bii); + putu2(bci); + bii += code_LocalVariableTable_span_O.getInt(); + putu2(to_bci(bii) - bci); + putref(code_LocalVariableTable_name_RU.getRefN()); + putref(code_LocalVariableTable_type_RS.getRefN()); + putu2(code_LocalVariableTable_slot.getInt()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTypeTable) : + aname = cp.sym[constant_pool::s_LocalVariableTypeTable]; + putu2(count = code_LocalVariableTypeTable_N.getInt()); + for (j = 0; j < count; j++) + { + int bii = code_LocalVariableTypeTable_bci_P.getInt(); + int bci = to_bci(bii); + putu2(bci); + bii += code_LocalVariableTypeTable_span_O.getInt(); + putu2(to_bci(bii) - bci); + putref(code_LocalVariableTypeTable_name_RU.getRefN()); + putref(code_LocalVariableTypeTable_type_RS.getRefN()); + putu2(code_LocalVariableTypeTable_slot.getInt()); + } + break; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Signature) : + aname = cp.sym[constant_pool::s_Signature]; + putref(class_Signature_RS.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Signature) : + aname = cp.sym[constant_pool::s_Signature]; + putref(field_Signature_RS.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Signature) : + aname = cp.sym[constant_pool::s_Signature]; + putref(method_Signature_RS.getRefN()); + break; + + case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Deprecated) : + case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Deprecated) : + case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Deprecated) : + aname = cp.sym[constant_pool::s_Deprecated]; + // no data + break; + } + } + + if (aname == nullptr) + { + // Unparse a compressor-defined attribute. + layout_definition *lo = ad.getLayout(idx); + if (lo == nullptr) + { + unpack_abort("bad layout index"); + break; + } + assert((int)lo->idx == idx); + aname = lo->nameEntry; + if (aname == nullptr) + { + bytes nameb; + nameb.set(lo->name); + aname = cp.ensureUtf8(nameb); + // Cache the name entry for next time. + lo->nameEntry = aname; + } + // Execute all the layout elements. + band **bands = lo->bands(); + if (lo->hasCallables()) + { + band &cble = *bands[0]; + assert(cble.le_kind == EK_CBLE); + bands = cble.le_body; + } + putlayout(bands); + } + + if (aname == nullptr) + unpack_abort("bad attribute index"); + + byte *wp1 = wp; + wp = wp_at(abase); + + // DTRT if this attr is on the strip-list. + // (Note that we emptied the data out of the band first.) + if (ad.strip_names.contains(aname)) + { + continue; + } + + // patch the name and length + putref(aname); + putu4((int)(wp1 - (wp + 4))); // put the attr size + wp = wp1; + na++; // count the attrs actually written + } + + if (na != na0) + // Refresh changed count. + putu2_at(wp_at(naOffset), na); + return na; +} + +void unpacker::write_members(int num, int attrc) +{ + attr_definitions &ad = attr_defs[attrc]; + band &member_flags_hi = ad.xxx_flags_hi(); + band &member_flags_lo = ad.xxx_flags_lo(); + band &member_descr = (&member_flags_hi)[e_field_descr - e_field_flags_hi]; + bool haveLongFlags = ad.haveLongFlags(); + + putu2(num); + uint64_t indexMask = attr_defs[attrc].flagIndexMask(); + for (int i = 0; i < num; i++) + { + uint64_t mflags = member_flags_hi.getLong(member_flags_lo, haveLongFlags); + entry *mdescr = member_descr.getRef(); + cur_descr = mdescr; + putu2(cur_descr_flags = (ushort)(mflags & ~indexMask)); + putref(mdescr->descrName()); + putref(mdescr->descrType()); + write_attrs(attrc, (mflags & indexMask)); + } + cur_descr = nullptr; +} + +extern "C" int raw_address_cmp(const void *p1p, const void *p2p) +{ + void *p1 = *(void **)p1p; + void *p2 = *(void **)p2p; + return (p1 > p2) ? 1 : (p1 < p2) ? -1 : 0; +} + +void unpacker::write_classfile_tail() +{ + cur_classfile_tail.empty(); + set_output(&cur_classfile_tail); + + int i, num; + + attr_definitions &ad = attr_defs[ATTR_CONTEXT_CLASS]; + + bool haveLongFlags = ad.haveLongFlags(); + uint64_t kflags = class_flags_hi.getLong(class_flags_lo, haveLongFlags); + uint64_t indexMask = ad.flagIndexMask(); + + cur_class = class_this.getRef(); + cur_super = class_super.getRef(); + + if (cur_super == cur_class) + cur_super = nullptr; + // special representation for java/lang/Object + + putu2((ushort)(kflags & ~indexMask)); + putref(cur_class); + putref(cur_super); + + putu2(num = class_interface_count.getInt()); + for (i = 0; i < num; i++) + { + putref(class_interface.getRef()); + } + + write_members(class_field_count.getInt(), ATTR_CONTEXT_FIELD); + write_members(class_method_count.getInt(), ATTR_CONTEXT_METHOD); + + cur_class_has_local_ics = false; // may be set true by write_attrs + + int naOffset = (int)wpoffset(); + int na = write_attrs(ATTR_CONTEXT_CLASS, (kflags & indexMask)); + +// at the very last, choose which inner classes (if any) pertain to k: +#ifdef ASSERT + for (i = 0; i < ic_count; i++) + { + assert(!ics[i].requested); + } +#endif + // First, consult the global table and the local constant pool, + // and decide on the globally implied inner classes. + // (Note that we read the cpool's outputIndex fields, but we + // do not yet write them, since the local IC attribute might + // reverse a global decision to declare an IC.) + assert(requested_ics.length() == 0); // must start out empty + // Always include all members of the current class. + for (inner_class *child = cp.getFirstChildIC(cur_class); child != nullptr; + child = cp.getNextChildIC(child)) + { + child->requested = true; + requested_ics.add(child); + } + // And, for each inner class mentioned in the constant pool, + // include it and all its outers. + int noes = cp.outputEntries.length(); + entry **oes = (entry **)cp.outputEntries.base(); + for (i = 0; i < noes; i++) + { + entry &e = *oes[i]; + if (e.tag != CONSTANT_Class) + continue; // wrong sort + for (inner_class *ic = cp.getIC(&e); ic != nullptr; ic = cp.getIC(ic->outer)) + { + if (ic->requested) + break; // already processed + ic->requested = true; + requested_ics.add(ic); + } + } + int local_ics = requested_ics.length(); + // Second, consult a local attribute (if any) and adjust the global set. + inner_class *extra_ics = nullptr; + int num_extra_ics = 0; + if (cur_class_has_local_ics) + { + // adjust the set of ICs by symmetric set difference w/ the locals + num_extra_ics = class_InnerClasses_N.getInt(); + if (num_extra_ics == 0) + { + // Explicit zero count has an irregular meaning: It deletes the attr. + local_ics = 0; // (short-circuit all tests of requested bits) + } + else + { + extra_ics = T_NEW(inner_class, num_extra_ics); + // Note: extra_ics will be freed up by next call to get_next_file(). + } + } + for (i = 0; i < num_extra_ics; i++) + { + inner_class &extra_ic = extra_ics[i]; + extra_ic.inner = class_InnerClasses_RC.getRef(); + // Find the corresponding equivalent global IC: + inner_class *global_ic = cp.getIC(extra_ic.inner); + int flags = class_InnerClasses_F.getInt(); + if (flags == 0) + { + // The extra IC is simply a copy of a global IC. + if (global_ic == nullptr) + { + unpack_abort("bad reference to inner class"); + break; + } + extra_ic = (*global_ic); // fill in rest of fields + } + else + { + flags &= ~ACC_IC_LONG_FORM; // clear high bit if set to get clean zero + extra_ic.flags = flags; + extra_ic.outer = class_InnerClasses_outer_RCN.getRefN(); + extra_ic.name = class_InnerClasses_name_RUN.getRefN(); + // Detect if this is an exact copy of the global tuple. + if (global_ic != nullptr) + { + if (global_ic->flags != extra_ic.flags || global_ic->outer != extra_ic.outer || + global_ic->name != extra_ic.name) + { + global_ic = nullptr; // not really the same, so break the link + } + } + } + if (global_ic != nullptr && global_ic->requested) + { + // This local repetition reverses the globally implied request. + global_ic->requested = false; + extra_ic.requested = false; + local_ics -= 1; + } + else + { + // The global either does not exist, or is not yet requested. + extra_ic.requested = true; + local_ics += 1; + } + } + // Finally, if there are any that survived, put them into an attribute. + // (Note that a zero-count attribute is always deleted.) + // The putref calls below will tell the constant pool to add any + // necessary local CP references to support the InnerClasses attribute. + // This step must be the last round of additions to the local CP. + if (local_ics > 0) + { + // append the new attribute: + putref(cp.sym[constant_pool::s_InnerClasses]); + putu4(2 + 2 * 4 * local_ics); + putu2(local_ics); + PTRLIST_QSORT(requested_ics, raw_address_cmp); + int num_global_ics = requested_ics.length(); + for (i = -num_global_ics; i < num_extra_ics; i++) + { + inner_class *ic; + if (i < 0) + ic = (inner_class *)requested_ics.get(num_global_ics + i); + else + ic = &extra_ics[i]; + if (ic->requested) + { + putref(ic->inner); + putref(ic->outer); + putref(ic->name); + putu2(ic->flags); + } + } + putu2_at(wp_at(naOffset), ++na); // increment class attr count + } + + // Tidy up global 'requested' bits: + for (i = requested_ics.length(); --i >= 0;) + { + inner_class *ic = (inner_class *)requested_ics.get(i); + ic->requested = false; + } + requested_ics.empty(); + + close_output(); + + // rewrite CP references in the tail + cp.computeOutputIndexes(); + int nextref = 0; + for (i = 0; i < (int)class_fixup_type.size(); i++) + { + int type = class_fixup_type.getByte(i); + byte *fixp = wp_at(class_fixup_offset.get(i)); + entry *e = (entry *)class_fixup_ref.get(nextref++); + int idx = e->getOutputIndex(); + switch (type) + { + case 1: + putu1_at(fixp, idx); + break; + case 2: + putu2_at(fixp, idx); + break; + default: + assert(false); // should not reach here + } + } +} + +void unpacker::write_classfile_head() +{ + cur_classfile_head.empty(); + set_output(&cur_classfile_head); + + putu4(JAVA_MAGIC); + putu2(cur_class_minver); + putu2(cur_class_majver); + putu2(cp.outputIndexLimit); + + int checkIndex = 1; + int noes = cp.outputEntries.length(); + entry **oes = (entry **)cp.outputEntries.base(); + for (int i = 0; i < noes; i++) + { + entry &e = *oes[i]; + assert(e.getOutputIndex() == checkIndex++); + byte tag = e.tag; + assert(tag != CONSTANT_Signature); + putu1(tag); + switch (tag) + { + case CONSTANT_Utf8: + putu2((int)e.value.b.len); + put_bytes(e.value.b); + break; + case CONSTANT_Integer: + case CONSTANT_Float: + putu4(e.value.i); + break; + case CONSTANT_Long: + case CONSTANT_Double: + putu8(e.value.l); + assert(checkIndex++); + break; + case CONSTANT_Class: + case CONSTANT_String: + // just write the ref + putu2(e.refs[0]->getOutputIndex()); + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_NameandType: + putu2(e.refs[0]->getOutputIndex()); + putu2(e.refs[1]->getOutputIndex()); + break; + default: + unpack_abort(ERROR_INTERNAL); + } + } + close_output(); +} + +unpacker::file *unpacker::get_next_file() +{ + free_temps(); + if (files_remaining == 0) + { + // Leave a clue that we're exhausted. + cur_file.name = nullptr; + cur_file.size = 0; + if (archive_size != 0) + { + uint64_t predicted_size = unsized_bytes_read + archive_size; + if (predicted_size != bytes_read) + unpack_abort("archive header had incorrect size"); + } + return nullptr; + } + files_remaining -= 1; + assert(files_written < file_count || classes_written < class_count); + cur_file.name = ""; + cur_file.size = 0; + cur_file.modtime = default_file_modtime; + cur_file.options = default_file_options; + cur_file.data[0].set(nullptr, 0); + cur_file.data[1].set(nullptr, 0); + if (files_written < file_count) + { + entry *e = file_name.getRef(); + cur_file.name = e->utf8String(); + bool haveLongSize = ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0); + cur_file.size = file_size_hi.getLong(file_size_lo, haveLongSize); + if ((archive_options & AO_HAVE_FILE_MODTIME) != 0) + cur_file.modtime += file_modtime.getInt(); // relative to archive modtime + if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0) + cur_file.options |= file_options.getInt() & ~suppress_file_options; + } + else if (classes_written < class_count) + { + // there is a class for a missing file record + cur_file.options |= FO_IS_CLASS_STUB; + } + if ((cur_file.options & FO_IS_CLASS_STUB) != 0) + { + assert(classes_written < class_count); + classes_written += 1; + if (cur_file.size != 0) + { + unpack_abort("class file size transmitted"); + } + reset_cur_classfile(); + + // write the meat of the classfile: + write_classfile_tail(); + cur_file.data[1] = cur_classfile_tail.b; + + // write the CP of the classfile, second: + write_classfile_head(); + cur_file.data[0] = cur_classfile_head.b; + + cur_file.size += cur_file.data[0].len; + cur_file.size += cur_file.data[1].len; + if (cur_file.name[0] == '\0') + { + bytes &prefix = cur_class->ref(0)->value.b; + const char *suffix = ".class"; + int len = (int)(prefix.len + strlen(suffix)); + bytes name; + name.set(T_NEW(byte, add_size(len, 1)), len); + cur_file.name = name.strcat(prefix).strcat(suffix).strval(); + } + } + else + { + // If there is buffered file data, produce a pointer to it. + if (cur_file.size != (size_t)cur_file.size) + { + // Silly size specified. + unpack_abort("resource file too large"); + } + size_t rpleft = input_remaining(); + if (rpleft > 0) + { + if (rpleft > cur_file.size) + rpleft = (size_t)cur_file.size; + cur_file.data[0].set(rp, rpleft); + rp += rpleft; + } + if (rpleft < cur_file.size) + { + // Caller must read the rest. + size_t fleft = (size_t)cur_file.size - rpleft; + bytes_read += fleft; // Credit it to the overall archive size. + } + } + bytes_written += cur_file.size; + files_written += 1; + return &cur_file; +} + +// Write a file to jarout. +void unpacker::write_file_to_jar(unpacker::file *f) +{ + size_t htsize = f->data[0].len + f->data[1].len; + uint64_t fsize = f->size; + if (htsize == fsize) + { + jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, f->data[0], f->data[1]); + } + else + { + assert(input_remaining() == 0); + bytes part1, part2; + part1.len = f->data[0].len; + part1.set(T_NEW(byte, part1.len), part1.len); + part1.copyFrom(f->data[0]); + assert(f->data[1].len == 0); + part2.set(nullptr, 0); + size_t fleft = (size_t)fsize - part1.len; + assert(bytes_read > fleft); // part2 already credited by get_next_file + bytes_read -= fleft; + if (fleft > 0) + { + // Must read some more. + if (live_input) + { + // Stop using the input buffer. Make a new one: + if (free_input) + input.free(); + input.init(fleft > (1 << 12) ? fleft : (1 << 12)); + free_input = true; + live_input = false; + } + else + { + // Make it large enough. + assert(free_input); // must be reallocable + input.ensureSize(fleft); + } + rplimit = rp = input.base(); + input.setLimit(rp + fleft); + if (!ensure_input(fleft)) + unpack_abort("EOF reading resource file"); + part2.ptr = input_scan(); + part2.len = input_remaining(); + rplimit = rp = input.base(); + } + jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, part1, part2); + } + if (verbose >= 3) + { + fprintf(stderr, "Wrote " LONG_LONG_FORMAT " bytes to: %s\n", fsize, f->name); + } +} diff --git a/depends/pack200/src/unpack.h b/depends/pack200/src/unpack.h new file mode 100644 index 00000000..0100700d --- /dev/null +++ b/depends/pack200/src/unpack.h @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// Global Structures +struct jar; +struct gunzip; +struct band; +struct constant_pool; +struct entry; +struct cpindex; +struct inner_class; +struct value_stream; + +struct cpindex +{ + uint32_t len; + entry *base1; // base of primary index + entry **base2; // base of secondary index + byte ixTag; // type of entries (!= CONSTANT_None), plus 64 if sub-index + enum + { + SUB_TAG = 64 + }; + + entry *get(uint32_t i); + + void init(int len_, entry *base1_, int ixTag_) + { + len = len_; + base1 = base1_; + base2 = nullptr; + ixTag = ixTag_; + } + void init(int len_, entry **base2_, int ixTag_) + { + len = len_; + base1 = nullptr; + base2 = base2_; + ixTag = ixTag_; + } +}; + +struct constant_pool +{ + uint32_t nentries; + entry *entries; + entry *first_extra_entry; + uint32_t maxentries; // total allocated size of entries + + // Position and size of each homogeneous subrange: + int tag_count[CONSTANT_Limit]; + int tag_base[CONSTANT_Limit]; + cpindex tag_index[CONSTANT_Limit]; + ptrlist tag_extras[CONSTANT_Limit]; + + cpindex *member_indexes; // indexed by 2*CONSTANT_Class.inord + cpindex *getFieldIndex(entry *classRef); + cpindex *getMethodIndex(entry *classRef); + + inner_class **ic_index; + inner_class **ic_child_index; + inner_class *getIC(entry *inner); + inner_class *getFirstChildIC(entry *outer); + inner_class *getNextChildIC(inner_class *child); + + int outputIndexLimit; // index limit after renumbering + ptrlist outputEntries; // list of entry* needing output idx assigned + + entry **hashTab; + uint32_t hashTabLength; + entry *&hashTabRef(byte tag, bytes &b); + entry *ensureUtf8(bytes &b); + entry *ensureClass(bytes &b); + + // Well-known Utf8 symbols. + enum + { +#define SNAME(n, s) s_##s, + ALL_ATTR_DO(SNAME) +#undef SNAME + s_lt_init_gt, // <init> + s_LIMIT + }; + entry *sym[s_LIMIT]; + + // read counts from hdr, allocate main arrays + enum + { + NUM_COUNTS = 12 + }; + void init(unpacker *u, int counts[NUM_COUNTS]); + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + int getCount(byte tag) + { + assert((uint32_t)tag < CONSTANT_Limit); + return tag_count[tag]; + } + cpindex *getIndex(byte tag) + { + assert((uint32_t)tag < CONSTANT_Limit); + return &tag_index[tag]; + } + cpindex *getKQIndex(); // uses cur_descr + + void expandSignatures(); + void initMemberIndexes(); + + void computeOutputOrder(); + void computeOutputIndexes(); + void resetOutputIndexes(); +}; + +/* + * The unpacker provides the entry points to the unpack engine, + * as well as maintains the state of the engine. + */ +struct unpacker +{ + // One element of the resulting JAR. + struct file + { + const char *name; + uint64_t size; + int modtime; + int options; + bytes data[2]; + // Note: If Sum(data[*].len) < size, + // remaining bytes must be read directly from the input stream. + bool deflate_hint() + { + return ((options & FO_DEFLATE_HINT) != 0); + } + }; + + // if running Unix-style, here are the inputs and outputs + FILE *infileptr; // buffered + bytes inbytes; // direct + gunzip *gzin; // gunzip filter, if any + jar *jarout; // output JAR file + + // pointer to self, for U_NEW macro + unpacker *u; + + ptrlist mallocs; // list of guys to free when we are all done + ptrlist tmallocs; // list of guys to free on next client request + fillbytes smallbuf; // supplies small alloc requests + fillbytes tsmallbuf; // supplies temporary small alloc requests + + // option management members + int verbose; // verbose level, 0 means no output + int deflate_hint_or_zero; // ==0 means not set, otherwise -1 or 1 + int modification_time_or_zero; + + // input stream + fillbytes input; // the whole block (size is predicted, has slop too) + bool live_input; // is the data in this block live? + bool free_input; // must the input buffer be freed? + byte *rp; // read pointer (< rplimit <= input.limit()) + byte *rplimit; // how much of the input block has been read? + uint64_t bytes_read; + int unsized_bytes_read; + + // callback to read at least one byte, up to available input + typedef int64_t (*read_input_fn_t)(unpacker *self, void *buf, int64_t minlen, + int64_t maxlen); + read_input_fn_t read_input_fn; + + // archive header fields + int magic, minver, majver; + size_t archive_size; + int archive_next_count, archive_options, archive_modtime; + int band_headers_size; + int file_count, attr_definition_count, ic_count, class_count; + int default_class_minver, default_class_majver; + int default_file_options, suppress_file_options; // not header fields + int default_archive_modtime, default_file_modtime; // not header fields + int code_count; // not a header field + int files_remaining; // not a header field + + // engine state + band *all_bands; // indexed by band_number + byte *meta_rp; // read-pointer into (copy of) band_headers + constant_pool cp; // all constant pool information + inner_class *ics; // InnerClasses + + // output stream + bytes output; // output block (either classfile head or tail) + byte *wp; // write pointer (< wplimit == output.limit()) + byte *wpbase; // write pointer starting address (<= wp) + byte *wplimit; // how much of the output block has been written? + + // output state + file cur_file; + entry *cur_class; // CONSTANT_Class entry + entry *cur_super; // CONSTANT_Class entry or nullptr + entry *cur_descr; // CONSTANT_NameandType entry + int cur_descr_flags; // flags corresponding to cur_descr + int cur_class_minver, cur_class_majver; + bool cur_class_has_local_ics; + fillbytes cur_classfile_head; + fillbytes cur_classfile_tail; + int files_written; // also tells which file we're working on + int classes_written; // also tells which class we're working on + uint64_t bytes_written; + intlist bcimap; + fillbytes class_fixup_type; + intlist class_fixup_offset; + ptrlist class_fixup_ref; + fillbytes code_fixup_type; // which format of branch operand? + intlist code_fixup_offset; // location of operand needing fixup + intlist code_fixup_source; // encoded ID of branch insn + ptrlist requested_ics; // which ics need output? + + // stats pertaining to multiple segments (updated on reset) + uint64_t bytes_read_before_reset; + uint64_t bytes_written_before_reset; + int files_written_before_reset; + int classes_written_before_reset; + int segments_read_before_reset; + + // attribute state + struct layout_definition + { + uint32_t idx; // index (0..31...) which identifies this layout + const char *name; // name of layout + entry *nameEntry; + const char *layout; // string of layout (not yet parsed) + band **elems; // array of top-level layout elems (or callables) + + bool hasCallables() + { + return layout[0] == '['; + } + band **bands() + { + assert(elems != nullptr); + return elems; + } + }; + struct attr_definitions + { + unpacker *u; // pointer to self, for U_NEW macro + int xxx_flags_hi_bn; // locator for flags, count, indexes, calls bands + int attrc; // ATTR_CONTEXT_CLASS, etc. + uint32_t flag_limit; // 32 or 63, depending on archive_options bit + uint64_t predef; // mask of built-in definitions + uint64_t redef; // mask of local flag definitions or redefinitions + ptrlist layouts; // local (compressor-defined) defs, in index order + int flag_count[X_ATTR_LIMIT_FLAGS_HI]; + intlist overflow_count; + ptrlist strip_names; // what attribute names are being stripped? + ptrlist band_stack; // Temp., used during layout parsing. + ptrlist calls_to_link; // (ditto) + int bands_made; // (ditto) + + void free() + { + layouts.free(); + overflow_count.free(); + strip_names.free(); + band_stack.free(); + calls_to_link.free(); + } + + // Locate the five fixed bands. + band &xxx_flags_hi(); + band &xxx_flags_lo(); + band &xxx_attr_count(); + band &xxx_attr_indexes(); + band &xxx_attr_calls(); + band &fixed_band(int e_class_xxx); + + // Register a new layout, and make bands for it. + layout_definition *defineLayout(int idx, const char *name, const char *layout); + layout_definition *defineLayout(int idx, entry *nameEntry, const char *layout); + band **buildBands(layout_definition *lo); + + // Parse a layout string or part of one, recursively if necessary. + const char *parseLayout(const char *lp, band **&res, int curCble); + const char *parseNumeral(const char *lp, int &res); + const char *parseIntLayout(const char *lp, band *&res, byte le_kind, + bool can_be_signed = false); + band **popBody(int band_stack_base); // pops a body off band_stack + + // Read data into the bands of the idx-th layout. + void readBandData(int idx); // parse layout, make bands, read data + void readBandData(band **body, uint32_t count); // recursive helper + + layout_definition *getLayout(uint32_t idx) + { + if (idx >= (uint32_t)layouts.length()) + return nullptr; + return (layout_definition *)layouts.get(idx); + } + + void setHaveLongFlags(bool z) + { + assert(flag_limit == 0); // not set up yet + flag_limit = (z ? X_ATTR_LIMIT_FLAGS_HI : X_ATTR_LIMIT_NO_FLAGS_HI); + } + bool haveLongFlags() + { + assert(flag_limit == X_ATTR_LIMIT_NO_FLAGS_HI || + flag_limit == X_ATTR_LIMIT_FLAGS_HI); + return flag_limit == X_ATTR_LIMIT_FLAGS_HI; + } + + // Return flag_count if idx is predef and not redef, else zero. + int predefCount(uint32_t idx); + + bool isRedefined(uint32_t idx) + { + if (idx >= flag_limit) + return false; + return (bool)((redef >> idx) & 1); + } + bool isPredefined(uint32_t idx) + { + if (idx >= flag_limit) + return false; + return (bool)(((predef & ~redef) >> idx) & 1); + } + uint64_t flagIndexMask() + { + return (predef | redef); + } + bool isIndex(uint32_t idx) + { + assert(flag_limit != 0); // must be set up already + if (idx < flag_limit) + return (bool)(((predef | redef) >> idx) & 1); + else + return (idx - flag_limit < (uint32_t)overflow_count.length()); + } + int &getCount(uint32_t idx) + { + assert(isIndex(idx)); + if (idx < flag_limit) + return flag_count[idx]; + else + return overflow_count.get(idx - flag_limit); + } + }; + + attr_definitions attr_defs[ATTR_CONTEXT_LIMIT]; + + // Initialization + void init(read_input_fn_t input_fn = nullptr); + // Resets to a known sane state + void reset(); + // Deallocates all storage. + void free(); + // Deallocates temporary storage (volatile after next client call). + void free_temps() + { + tsmallbuf.init(); + tmallocs.freeAll(); + } + + // Option management methods + bool set_option(const char *option, const char *value); + const char *get_option(const char *option); + + // Fetching input. + bool ensure_input(int64_t more); + byte *input_scan() + { + return rp; + } + size_t input_remaining() + { + return rplimit - rp; + } + size_t input_consumed() + { + return rp - input.base(); + } + + // Entry points to the unpack engine + static int run(int argc, char **argv); // Unix-style entry point. + void check_options(); + void start(void *packptr = nullptr, size_t len = 0); + void write_file_to_jar(file *f); + void finish(); + + // Public post unpack methods + int get_files_remaining() + { + return files_remaining; + } + int get_segments_remaining() + { + return archive_next_count; + } + file *get_next_file(); // returns nullptr on last file + + // General purpose methods + void *alloc(size_t size) + { + return alloc_heap(size, true); + } + void *temp_alloc(size_t size) + { + return alloc_heap(size, true, true); + } + void *alloc_heap(size_t size, bool smallOK = false, bool temp = false); + void saveTo(bytes &b, const char *str) + { + saveTo(b, (byte *)str, strlen(str)); + } + void saveTo(bytes &b, bytes &data) + { + saveTo(b, data.ptr, data.len); + } + void saveTo(bytes &b, byte *ptr, size_t len); //{ b.ptr = U_NEW...} + const char *saveStr(const char *str) + { + bytes buf; + saveTo(buf, str); + return buf.strval(); + } + const char *saveIntStr(int num) + { + char buf[30]; + sprintf(buf, "%d", num); + return saveStr(buf); + } + static unpacker *current(); // find current instance + + // Output management + void set_output(fillbytes *which) + { + assert(wp == nullptr); + which->ensureSize(1 << 12); // covers the average classfile + wpbase = which->base(); + wp = which->limit(); + wplimit = which->end(); + } + fillbytes *close_output(fillbytes *which = nullptr); // inverse of set_output + + // These take an implicit parameter of wp/wplimit, and resize as necessary: + byte *put_space(size_t len); // allocates space at wp, returns pointer + size_t put_empty(size_t s) + { + byte *p = put_space(s); + return p - wpbase; + } + void ensure_put_space(size_t len); + void put_bytes(bytes &b) + { + b.writeTo(put_space(b.len)); + } + void putu1(int n) + { + putu1_at(put_space(1), n); + } + void putu1_fast(int n) + { + putu1_at(wp++, n); + } + void putu2(int n); // { putu2_at(put_space(2), n); } + void putu4(int n); // { putu4_at(put_space(4), n); } + void putu8(int64_t n); // { putu8_at(put_space(8), n); } + void putref(entry *e); // { putu2_at(put_space(2), putref_index(e, 2)); } + void putu1ref(entry *e); // { putu1_at(put_space(1), putref_index(e, 1)); } + int putref_index(entry *e, int size); // size in [1..2] + void put_label(int curIP, int size); // size in {2,4} + void putlayout(band **body); + void put_stackmap_type(); + + size_t wpoffset() + { + return (size_t)(wp - wpbase); + } // (unvariant across overflow) + byte *wp_at(size_t offset) + { + return wpbase + offset; + } + uint32_t to_bci(uint32_t bii); + void get_code_header(int &max_stack, int &max_na_locals, int &handler_count, int &cflags); + band *ref_band_for_self_op(int bc, bool &isAloadVar, int &origBCVar); + band *ref_band_for_op(int bc); + + // Definitions of standard classfile int formats: + static void putu1_at(byte *wp, int n) + { + assert(n == (n & 0xFF)); + wp[0] = n; + } + static void putu2_at(byte *wp, int n); + static void putu4_at(byte *wp, int n); + static void putu8_at(byte *wp, int64_t n); + + // Private stuff + void reset_cur_classfile(); + void write_classfile_tail(); + void write_classfile_head(); + void write_code(); + void write_bc_ops(); + void write_members(int num, int attrc); // attrc=ATTR_CONTEXT_FIELD/METHOD + int write_attrs(int attrc, uint64_t indexBits); + + // The readers + void read_bands(); + void read_file_header(); + void read_cp(); + void read_cp_counts(value_stream &hdr); + void read_attr_defs(); + void read_ics(); + void read_attrs(int attrc, int obj_count); + void read_classes(); + void read_code_headers(); + void read_bcs(); + void read_bc_ops(); + void read_files(); + void read_Utf8_values(entry *cpMap, int len); + void read_single_words(band &cp_band, entry *cpMap, int len); + void read_double_words(band &cp_bands, entry *cpMap, int len); + void read_single_refs(band &cp_band, byte refTag, entry *cpMap, int len); + void read_double_refs(band &cp_band, byte ref1Tag, byte ref2Tag, entry *cpMap, int len); + void read_signature_values(entry *cpMap, int len); +}; diff --git a/depends/pack200/src/unpack200.cpp b/depends/pack200/src/unpack200.cpp new file mode 100644 index 00000000..2ff8c34a --- /dev/null +++ b/depends/pack200/src/unpack200.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +#include <sys/types.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <time.h> +#include <stdint.h> + +#include "constants.h" +#include "utils.h" +#include "defines.h" +#include "bytes.h" +#include "coding.h" +#include "unpack200.h" +#include "unpack.h" +#include "zip.h" + +// Callback for fetching data, Unix style. +static int64_t read_input_via_stdio(unpacker *u, void *buf, int64_t minlen, int64_t maxlen) +{ + assert(u->infileptr != nullptr); + assert(minlen <= maxlen); // don't talk nonsense + int64_t numread = 0; + char *bufptr = (char *)buf; + while (numread < minlen) + { + // read available input, up to buf.length or maxlen + int readlen = (1 << 16); + if (readlen > (maxlen - numread)) + readlen = (int)(maxlen - numread); + int nr = 0; + + nr = (int)fread(bufptr, 1, readlen, u->infileptr); + if (nr <= 0) + { + if (errno != EINTR) + break; + nr = 0; + } + numread += nr; + bufptr += nr; + assert(numread <= maxlen); + } + return numread; +} + +enum +{ + EOF_MAGIC = 0, + BAD_MAGIC = -1 +}; + +static int read_magic(unpacker *u, char peek[], int peeklen) +{ + assert(peeklen == 4); // magic numbers are always 4 bytes + int64_t nr = (u->read_input_fn)(u, peek, peeklen, peeklen); + if (nr != peeklen) + { + return (nr == 0) ? EOF_MAGIC : BAD_MAGIC; + } + int magic = 0; + for (int i = 0; i < peeklen; i++) + { + magic <<= 8; + magic += peek[i] & 0xFF; + } + return magic; +} + +void unpack_200(std::string input_path, std::string output_path) +{ + unpacker u; + int status = 0; + + FILE *input = fopen(input_path.c_str(), "rb"); + if (!input) + { + throw std::runtime_error("Can't open input file" + input_path); + } + FILE *output = fopen(output_path.c_str(), "wb"); + if (!output) + { + fclose(output); + throw std::runtime_error("Can't open output file" + output_path); + } + u.init(read_input_via_stdio); + + // initialize jar output + // the output takes ownership of the file handle + jar jarout; + jarout.init(&u); + jarout.jarfp = output; + + // the input doesn't + u.infileptr = input; + + // read the magic! + char peek[4]; + int magic; + magic = read_magic(&u, peek, (int)sizeof(peek)); + + // if it is a gzip encoded file, we need an extra gzip input filter + if ((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC) + { + gunzip *gzin = NEW(gunzip, 1); + gzin->init(&u); + // FIXME: why the side effects? WHY? + u.gzin->start(magic); + u.start(); + } + else + { + // otherwise, feed the bytes to the unpacker directly + u.start(peek, sizeof(peek)); + } + + // Note: The checks to u.aborting() are necessary to gracefully + // terminate processing when the first segment throws an error. + for (;;) + { + // Each trip through this loop unpacks one segment + // and then resets the unpacker. + for (unpacker::file *filep; (filep = u.get_next_file()) != nullptr;) + { + u.write_file_to_jar(filep); + } + + // Peek ahead for more data. + magic = read_magic(&u, peek, (int)sizeof(peek)); + if (magic != (int)JAVA_PACKAGE_MAGIC) + { + // we do not feel strongly about this kind of thing... + /* + if (magic != EOF_MAGIC) + unpack_abort("garbage after end of pack archive"); + */ + break; // all done + } + + // Release all storage from parsing the old segment. + u.reset(); + // Restart, beginning with the peek-ahead. + u.start(peek, sizeof(peek)); + } + u.finish(); + u.free(); // tidy up malloc blocks + fclose(input); +} diff --git a/depends/pack200/src/utils.cpp b/depends/pack200/src/utils.cpp new file mode 100644 index 00000000..0b7d91ca --- /dev/null +++ b/depends/pack200/src/utils.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <assert.h> +#include <stdint.h> + +#include <sys/stat.h> + +#ifdef _MSC_VER +#include <direct.h> +#include <io.h> +#include <process.h> +#else +#include <unistd.h> +#endif + +#include "constants.h" +#include "defines.h" +#include "bytes.h" +#include "utils.h" + +#include "unpack.h" + +void *must_malloc(size_t size) +{ + size_t msize = size; + void *ptr = (msize > PSIZE_MAX) ? nullptr : malloc(msize); + if (ptr != nullptr) + { + memset(ptr, 0, size); + } + else + { + throw std::runtime_error(ERROR_ENOMEM); + } + return ptr; +} + +void unpack_abort(const char *msg) +{ + if (msg == nullptr) + msg = "corrupt pack file or internal error"; + throw std::runtime_error(msg); +} diff --git a/depends/pack200/src/utils.h b/depends/pack200/src/utils.h new file mode 100644 index 00000000..5a3dc8f6 --- /dev/null +++ b/depends/pack200/src/utils.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// Definitions of our util functions + +#include <stdexcept> + +void *must_malloc(size_t size); + +// overflow management +#define OVERFLOW ((size_t) - 1) +#define PSIZE_MAX (OVERFLOW / 2) /* normal size limit */ + +inline size_t scale_size(size_t size, size_t scale) +{ + return (size > PSIZE_MAX / scale) ? OVERFLOW : size * scale; +} + +inline size_t add_size(size_t size1, size_t size2) +{ + return ((size1 | size2 | (size1 + size2)) > PSIZE_MAX) ? OVERFLOW : size1 + size2; +} + +inline size_t add_size(size_t size1, size_t size2, int size3) +{ + return add_size(add_size(size1, size2), size3); +} + +struct unpacker; +/// This throws an exception! +extern void unpack_abort(const char *msg = nullptr); diff --git a/depends/pack200/src/zip.cpp b/depends/pack200/src/zip.cpp new file mode 100644 index 00000000..32e8bd50 --- /dev/null +++ b/depends/pack200/src/zip.cpp @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Note: Lifted from uncrunch.c from jdk sources + */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <stdint.h> + +#include <stdlib.h> +#include <assert.h> + +#ifndef _MSC_VER +#include <strings.h> +#endif + +#include "defines.h" +#include "bytes.h" +#include "utils.h" + +#include "constants.h" +#include "unpack.h" + +#include "zip.h" + +#include "zlib.h" + +inline uint32_t jar::get_crc32(uint32_t c, uchar *ptr, uint32_t len) +{ + return crc32(c, ptr, len); +} + +// FIXME: this is bullshit. Do real endianness detection. +#ifdef sparc +#define SWAP_BYTES(a) ((((a) << 8) & 0xff00) | 0x00ff) & (((a) >> 8) | 0xff00) +#else +#define SWAP_BYTES(a) (a) +#endif + +#define GET_INT_LO(a) SWAP_BYTES(a & 0xFFFF) + +#define GET_INT_HI(a) SWAP_BYTES((a >> 16) & 0xFFFF); + +void jar::init(unpacker *u_) +{ + BYTES_OF(*this).clear(); + u = u_; + u->jarout = this; +} + +// Write data to the ZIP output stream. +void jar::write_data(void *buff, int len) +{ + while (len > 0) + { + int rc = (int)fwrite(buff, 1, len, jarfp); + if (rc <= 0) + { + fprintf(stderr, "Error: write on output file failed err=%d\n", errno); + exit(1); // Called only from the native standalone unpacker + } + output_file_offset += rc; + buff = ((char *)buff) + rc; + len -= rc; + } +} + +void jar::add_to_jar_directory(const char *fname, bool store, int modtime, int len, int clen, + uint32_t crc) +{ + uint32_t fname_length = (uint32_t)strlen(fname); + ushort header[23]; + if (modtime == 0) + modtime = default_modtime; + uint32_t dostime = get_dostime(modtime); + + header[0] = (ushort)SWAP_BYTES(0x4B50); + header[1] = (ushort)SWAP_BYTES(0x0201); + header[2] = (ushort)SWAP_BYTES(0xA); + + // required version + header[3] = (ushort)SWAP_BYTES(0xA); + + // flags 02 = maximum sub-compression flag + header[4] = (store) ? 0x0 : SWAP_BYTES(0x2); + + // Compression method 8=deflate. + header[5] = (store) ? 0x0 : SWAP_BYTES(0x08); + + // Last modified date and time. + header[6] = (ushort)GET_INT_LO(dostime); + header[7] = (ushort)GET_INT_HI(dostime); + + // CRC + header[8] = (ushort)GET_INT_LO(crc); + header[9] = (ushort)GET_INT_HI(crc); + + // Compressed length: + header[10] = (ushort)GET_INT_LO(clen); + header[11] = (ushort)GET_INT_HI(clen); + + // Uncompressed length. + header[12] = (ushort)GET_INT_LO(len); + header[13] = (ushort)GET_INT_HI(len); + + // Filename length + header[14] = (ushort)SWAP_BYTES(fname_length); + // So called "extra field" length. + header[15] = 0; + // So called "comment" length. + header[16] = 0; + // Disk number start + header[17] = 0; + // File flags => binary + header[18] = 0; + // More file flags + header[19] = 0; + header[20] = 0; + // Offset within ZIP file. + header[21] = (ushort)GET_INT_LO(output_file_offset); + header[22] = (ushort)GET_INT_HI(output_file_offset); + + // Copy the whole thing into the central directory. + central_directory.append(header, sizeof(header)); + + // Copy the fname to the header. + central_directory.append(fname, fname_length); + + central_directory_count++; +} + +void jar::write_jar_header(const char *fname, bool store, int modtime, int len, int clen, + uint32_t crc) +{ + uint32_t fname_length = (uint32_t)strlen(fname); + ushort header[15]; + if (modtime == 0) + modtime = default_modtime; + uint32_t dostime = get_dostime(modtime); + + // ZIP LOC magic. + header[0] = (ushort)SWAP_BYTES(0x4B50); + header[1] = (ushort)SWAP_BYTES(0x0403); + + // Version + header[2] = (ushort)SWAP_BYTES(0xA); + + // flags 02 = maximum sub-compression flag + header[3] = (store) ? 0x0 : SWAP_BYTES(0x2); + + // Compression method = deflate + header[4] = (store) ? 0x0 : SWAP_BYTES(0x08); + + // Last modified date and time. + header[5] = (ushort)GET_INT_LO(dostime); + header[6] = (ushort)GET_INT_HI(dostime); + + // CRC + header[7] = (ushort)GET_INT_LO(crc); + header[8] = (ushort)GET_INT_HI(crc); + + // Compressed length: + header[9] = (ushort)GET_INT_LO(clen); + header[10] = (ushort)GET_INT_HI(clen); + + // Uncompressed length. + header[11] = (ushort)GET_INT_LO(len); + header[12] = (ushort)GET_INT_HI(len); + + // Filename length + header[13] = (ushort)SWAP_BYTES(fname_length); + // So called "extra field" length. + header[14] = 0; + + // Write the LOC header to the output file. + write_data(header, (int)sizeof(header)); + + // Copy the fname to the header. + write_data((char *)fname, (int)fname_length); +} + +void jar::write_central_directory() +{ + bytes mc; + mc.set("PACK200"); + + ushort header[11]; + + // Create the End of Central Directory structure. + header[0] = (ushort)SWAP_BYTES(0x4B50); + header[1] = (ushort)SWAP_BYTES(0x0605); + // disk numbers + header[2] = 0; + header[3] = 0; + // Number of entries in central directory. + header[4] = (ushort)SWAP_BYTES(central_directory_count); + header[5] = (ushort)SWAP_BYTES(central_directory_count); + // Size of the central directory} + header[6] = (ushort)GET_INT_LO((int)central_directory.size()); + header[7] = (ushort)GET_INT_HI((int)central_directory.size()); + // Offset of central directory within disk. + header[8] = (ushort)GET_INT_LO(output_file_offset); + header[9] = (ushort)GET_INT_HI(output_file_offset); + // zipfile comment length; + header[10] = (ushort)SWAP_BYTES((int)mc.len); + + // Write the central directory. + write_data(central_directory.b); + + // Write the End of Central Directory structure. + write_data(header, (int)sizeof(header)); + + // Write the comment. + write_data(mc); +} + +// Public API + +// Open a Jar file and initialize. +void jar::openJarFile(const char *fname) +{ + if (!jarfp) + { + jarfp = fopen(fname, "wb"); + if (!jarfp) + { + fprintf(stderr, "Error: Could not open jar file: %s\n", fname); + exit(3); // Called only from the native standalone unpacker + } + } +} + +// Add a ZIP entry and copy the file data +void jar::addJarEntry(const char *fname, bool deflate_hint, int modtime, bytes &head, + bytes &tail) +{ + int len = (int)(head.len + tail.len); + int clen = 0; + + uint32_t crc = get_crc32(0, Z_NULL, 0); + if (head.len != 0) + crc = get_crc32(crc, (uchar *)head.ptr, (uint32_t)head.len); + if (tail.len != 0) + crc = get_crc32(crc, (uchar *)tail.ptr, (uint32_t)tail.len); + + bool deflate = (deflate_hint && len > 0); + + if (deflate) + { + if (deflate_bytes(head, tail) == false) + { + deflate = false; + } + } + clen = (int)((deflate) ? deflated.size() : len); + add_to_jar_directory(fname, !deflate, modtime, len, clen, crc); + write_jar_header(fname, !deflate, modtime, len, clen, crc); + + if (deflate) + { + write_data(deflated.b); + } + else + { + write_data(head); + write_data(tail); + } +} + +// Add a ZIP entry for a directory name no data +void jar::addDirectoryToJarFile(const char *dir_name) +{ + bool store = true; + add_to_jar_directory((const char *)dir_name, store, default_modtime, 0, 0, 0); + write_jar_header((const char *)dir_name, store, default_modtime, 0, 0, 0); +} + +// Write out the central directory and close the jar file. +void jar::closeJarFile(bool central) +{ + if (jarfp) + { + fflush(jarfp); + if (central) + write_central_directory(); + fflush(jarfp); + fclose(jarfp); + } + reset(); +} + +/* Convert the date y/n/d and time h:m:s to a four byte DOS date and + * time (date in high two bytes, time in low two bytes allowing magnitude + * comparison). + */ +inline uint32_t jar::dostime(int y, int n, int d, int h, int m, int s) +{ + return y < 1980 ? dostime(1980, 1, 1, 0, 0, 0) + : (((uint32_t)y - 1980) << 25) | ((uint32_t)n << 21) | ((uint32_t)d << 16) | + ((uint32_t)h << 11) | ((uint32_t)m << 5) | ((uint32_t)s >> 1); +} +/* +#ifdef _REENTRANT // solaris +extern "C" struct tm *gmtime_r(const time_t *, struct tm *); +#else +#define gmtime_r(t, s) gmtime(t) +#endif +*/ +/* + * Return the Unix time in DOS format + */ +uint32_t jar::get_dostime(int modtime) +{ + // see defines.h + if (modtime != 0 && modtime == modtime_cache) + return dostime_cache; + if (modtime != 0 && default_modtime == 0) + default_modtime = modtime; // catch a reasonable default + time_t t = modtime; + struct tm sbuf; + (void)memset((void *)&sbuf, 0, sizeof(sbuf)); + struct tm *s = gmtime_r(&t, &sbuf); + modtime_cache = modtime; + dostime_cache = + dostime(s->tm_year + 1900, s->tm_mon + 1, s->tm_mday, s->tm_hour, s->tm_min, s->tm_sec); + // printf("modtime %d => %d\n", modtime_cache, dostime_cache); + return dostime_cache; +} + +/* Returns true on success, and will set the clen to the compressed + length, the caller should verify if true and clen less than the + input data +*/ +bool jar::deflate_bytes(bytes &head, bytes &tail) +{ + int len = (int)(head.len + tail.len); + + z_stream zs; + BYTES_OF(zs).clear(); + + // NOTE: the window size should always be -MAX_WBITS normally -15. + // unzip/zipup.c and java/Deflater.c + + int error = + deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + if (error != Z_OK) + { + /* + switch (error) + { + case Z_MEM_ERROR: + PRINTCR((2, "Error: deflate error : Out of memory \n")); + break; + case Z_STREAM_ERROR: + PRINTCR((2, "Error: deflate error : Invalid compression level \n")); + break; + case Z_VERSION_ERROR: + PRINTCR((2, "Error: deflate error : Invalid version\n")); + break; + default: + PRINTCR((2, "Error: Internal deflate error error = %d\n", error)); + } + */ + return false; + } + + deflated.empty(); + zs.next_out = (uchar *)deflated.grow(len + (len / 2)); + zs.avail_out = (int)deflated.size(); + + zs.next_in = (uchar *)head.ptr; + zs.avail_in = (int)head.len; + + bytes *first = &head; + bytes *last = &tail; + if (last->len == 0) + { + first = nullptr; + last = &head; + } + else if (first->len == 0) + { + first = nullptr; + } + + if (first != nullptr && error == Z_OK) + { + zs.next_in = (uchar *)first->ptr; + zs.avail_in = (int)first->len; + error = deflate(&zs, Z_NO_FLUSH); + } + if (error == Z_OK) + { + zs.next_in = (uchar *)last->ptr; + zs.avail_in = (int)last->len; + error = deflate(&zs, Z_FINISH); + } + if (error == Z_STREAM_END) + { + if (len > (int)zs.total_out) + { + deflated.b.len = zs.total_out; + deflateEnd(&zs); + return true; + } + deflateEnd(&zs); + return false; + } + + deflateEnd(&zs); + return false; +} + +// Callback for fetching data from a GZIP input stream +static int64_t read_input_via_gzip(unpacker *u, void *buf, int64_t minlen, int64_t maxlen) +{ + assert(minlen <= maxlen); // don't talk nonsense + int64_t numread = 0; + char *bufptr = (char *)buf; + char *inbuf = u->gzin->inbuf; + size_t inbuflen = sizeof(u->gzin->inbuf); + unpacker::read_input_fn_t read_gzin_fn = (unpacker::read_input_fn_t)u->gzin->read_input_fn; + z_stream &zs = *(z_stream *)u->gzin->zstream; + while (numread < minlen) + { + int readlen = (1 << 16); // pretty arbitrary + if (readlen > (maxlen - numread)) + readlen = (int)(maxlen - numread); + zs.next_out = (uchar *)bufptr; + zs.avail_out = readlen; + if (zs.avail_in == 0) + { + zs.avail_in = (int)read_gzin_fn(u, inbuf, 1, inbuflen); + zs.next_in = (uchar *)inbuf; + } + int error = inflate(&zs, Z_NO_FLUSH); + if (error != Z_OK && error != Z_STREAM_END) + { + unpack_abort("error inflating input"); + break; + } + int nr = readlen - zs.avail_out; + numread += nr; + bufptr += nr; + assert(numread <= maxlen); + if (error == Z_STREAM_END) + { + enum + { + TRAILER_LEN = 8 + }; + // skip 8-byte trailer + if (zs.avail_in >= TRAILER_LEN) + { + zs.avail_in -= TRAILER_LEN; + } + else + { + // Bug: 5023768,we read past the TRAILER_LEN to see if there is + // any extraneous data, as we dont support concatenated .gz + // files just yet. + int extra = (int)read_gzin_fn(u, inbuf, 1, inbuflen); + zs.avail_in += extra - TRAILER_LEN; + } + // %%% should check final CRC and length here + // %%% should check for concatenated *.gz files here + if (zs.avail_in > 0) + unpack_abort("garbage after end of deflated input stream"); + // pop this filter off: + u->gzin->free(); + break; + } + } + + // fprintf(u->errstrm, "readInputFn(%d,%d) => %d (gunzip)\n", + // (int)minlen, (int)maxlen, (int)numread); + return numread; +} + +void gunzip::init(unpacker *u_) +{ + BYTES_OF(*this).clear(); + u = u_; + assert(u->gzin == nullptr); // once only, please + read_input_fn = (void *)u->read_input_fn; + zstream = NEW(z_stream, 1); + u->gzin = this; + u->read_input_fn = read_input_via_gzip; +} + +void gunzip::start(int magic) +{ + assert((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC); + int gz_flg = (magic & 0xFF); // keep "flg", discard other 3 bytes + enum + { + FHCRC = (1 << 1), + FEXTRA = (1 << 2), + FNAME = (1 << 3), + FCOMMENT = (1 << 4) + }; + char gz_mtime[4]; + char gz_xfl[1]; + char gz_os[1]; + char gz_extra_len[2]; + char gz_hcrc[2]; + char gz_ignore; + // do not save extra, name, comment + read_fixed_field(gz_mtime, sizeof(gz_mtime)); + read_fixed_field(gz_xfl, sizeof(gz_xfl)); + read_fixed_field(gz_os, sizeof(gz_os)); + if (gz_flg & FEXTRA) + { + read_fixed_field(gz_extra_len, sizeof(gz_extra_len)); + int extra_len = gz_extra_len[0] & 0xFF; + extra_len += (gz_extra_len[1] & 0xFF) << 8; + for (; extra_len > 0; extra_len--) + { + read_fixed_field(&gz_ignore, 1); + } + } + int null_terms = 0; + if (gz_flg & FNAME) + null_terms++; + if (gz_flg & FCOMMENT) + null_terms++; + for (; null_terms; null_terms--) + { + for (;;) + { + gz_ignore = 0; + read_fixed_field(&gz_ignore, 1); + if (gz_ignore == 0) + break; + } + } + if (gz_flg & FHCRC) + read_fixed_field(gz_hcrc, sizeof(gz_hcrc)); + + // now the input stream is ready to read into the inflater + int error = inflateInit2((z_stream *)zstream, -MAX_WBITS); + if (error != Z_OK) + { + unpack_abort("cannot create input"); + } +} + +void gunzip::free() +{ + assert(u->gzin == this); + u->gzin = nullptr; + u->read_input_fn = (unpacker::read_input_fn_t) this->read_input_fn; + inflateEnd((z_stream *)zstream); + ::free(zstream); + zstream = nullptr; + ::free(this); +} + +void gunzip::read_fixed_field(char *buf, size_t buflen) +{ + int64_t nr = ((unpacker::read_input_fn_t)read_input_fn)(u, buf, buflen, buflen); + if ((size_t)nr != buflen) + unpack_abort("short stream header"); +} diff --git a/depends/pack200/src/zip.h b/depends/pack200/src/zip.h new file mode 100644 index 00000000..67ec24da --- /dev/null +++ b/depends/pack200/src/zip.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +#include <stdint.h> +typedef unsigned short ushort; +typedef unsigned int uint32_t; +typedef unsigned char uchar; + +struct unpacker; + +struct jar +{ + // JAR file writer + FILE *jarfp; + int default_modtime; + + // Used by unix2dostime: + int modtime_cache; + uint32_t dostime_cache; + + // Private members + fillbytes central_directory; + ushort central_directory_count; + uint32_t output_file_offset; + fillbytes deflated; // temporary buffer + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + // Public Methods + void openJarFile(const char *fname); + void addJarEntry(const char *fname, bool deflate_hint, int modtime, bytes &head, + bytes &tail); + void addDirectoryToJarFile(const char *dir_name); + void closeJarFile(bool central); + + void init(unpacker *u_); + + void free() + { + central_directory.free(); + deflated.free(); + } + + void reset() + { + free(); + init(u); + } + + // Private Methods + void write_data(void *ptr, int len); + void write_data(bytes &b) + { + write_data(b.ptr, (int)b.len); + } + void add_to_jar_directory(const char *fname, bool store, int modtime, int len, int clen, + uint32_t crc); + void write_jar_header(const char *fname, bool store, int modtime, int len, int clen, + unsigned int crc); + void write_central_directory(); + uint32_t dostime(int y, int n, int d, int h, int m, int s); + uint32_t get_dostime(int modtime); + + // The definitions of these depend on the NO_ZLIB option: + bool deflate_bytes(bytes &head, bytes &tail); + static uint32_t get_crc32(uint32_t c, unsigned char *ptr, uint32_t len); +}; + +struct gunzip +{ + // optional gzip input stream control block + + // pointer to outer unpacker, for error checks etc. + unpacker *u; + + void *read_input_fn; // underlying \bchar\b stream + void *zstream; // inflater state + char inbuf[1 << 14]; // input buffer + + void init(unpacker *u_); // pushes new value on u->read_input_fn + + void free(); + + void start(int magic); + + // private stuff + void read_fixed_field(char *buf, size_t buflen); +}; diff --git a/depends/quazip/CMakeLists.txt b/depends/quazip/CMakeLists.txt index a9adcefe..76da0a59 100644 --- a/depends/quazip/CMakeLists.txt +++ b/depends/quazip/CMakeLists.txt @@ -5,7 +5,8 @@ project(quazip) IF(UNIX) find_package(ZLIB REQUIRED) ELSE(UNIX) - SET(ZLIB_INCLUDE_DIRS "${QT_ROOT}/src/3rdparty/zlib" CACHE PATH "Path to ZLIB headers of Qt") + get_filename_component (ZLIB_FOUND_DIR "${Qt5Core_DIR}/../../../include/QtZlib" ABSOLUTE) + SET(ZLIB_INCLUDE_DIRS ${ZLIB_FOUND_DIR} CACHE PATH "Path to ZLIB headers of Qt") SET(ZLIB_LIBRARIES "") IF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h") MESSAGE("Please specify a valid zlib include dir") @@ -31,7 +32,7 @@ ADD_DEFINITIONS(-DQUAZIP_STATIC) #qt5_wrap_cpp(MOC_SRCS ${PUBLIC_HEADERS}) #set(SRCS ${SRCS} ${MOC_SRCS}) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) +#set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_library(quazip STATIC ${SRCS}) QT5_USE_MODULES(quazip Core) diff --git a/depends/util/src/pathutils.cpp b/depends/util/src/pathutils.cpp index 0836567d..4c24fa5d 100644 --- a/depends/util/src/pathutils.cpp +++ b/depends/util/src/pathutils.cpp @@ -75,7 +75,6 @@ bool ensureFilePathExists(QString filenamepath) QDir dir; QString ensuredPath = a.path(); bool success = dir.mkpath ( ensuredPath ); - qDebug() << "ensureFilePathExists:" << success << ensuredPath << filenamepath; return success; } @@ -85,7 +84,6 @@ bool ensureFolderPathExists(QString foldernamepath) QDir dir; QString ensuredPath = a.filePath(); bool success = dir.mkpath ( ensuredPath ); - qDebug() << "ensureFolderPathExists:" << success << ensuredPath << foldernamepath; return success; } diff --git a/depends/xz-embedded/CMakeLists.txt b/depends/xz-embedded/CMakeLists.txt new file mode 100644 index 00000000..d4987f76 --- /dev/null +++ b/depends/xz-embedded/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 2.6) +project(xz-embedded) + +option(XZ_BUILD_BCJ "Build xz-embedded with BCJ support (native binary optimization)" OFF) +option(XZ_BUILD_CRC64 "Build xz-embedded with CRC64 checksum support" ON) +option(XZ_BUILD_MINIDEC "Build a tiny utility that decompresses xz streams" OFF) + +set(CMAKE_C_FLAGS "-std=c99") + +include_directories(include) +SET(XZ_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) + +# See include/xz.h for manual feature configuration +# tweak this list and xz.h to fit your needs + +set(XZ_SOURCES +include/xz.h +src/xz_config.h +src/xz_crc32.c +src/xz_crc64.c +src/xz_dec_lzma2.c +src/xz_dec_stream.c +src/xz_lzma2.h +src/xz_private.h +src/xz_stream.h +# src/xz_dec_bcj.c +) +# TODO: look into what would be needed for plain old lzma + +add_library(xz-embedded STATIC ${XZ_SOURCES}) +add_executable(xzminidec xzminidec.c) +target_link_libraries(xzminidec xz-embedded) diff --git a/depends/xz-embedded/include/xz.h b/depends/xz-embedded/include/xz.h new file mode 100644 index 00000000..49a96f7b --- /dev/null +++ b/depends/xz-embedded/include/xz.h @@ -0,0 +1,319 @@ +/* + * XZ decompressor + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_H +#define XZ_H + +#ifdef __KERNEL__ +# include <linux/stddef.h> +# include <linux/types.h> +#else +# include <stddef.h> +# include <stdint.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Definitions that determine available features */ +#define XZ_DEC_ANY_CHECK 1 +#define XZ_USE_CRC64 1 + +// native machine code compression stuff +/* +#define XZ_DEC_X86 +#define XZ_DEC_POWERPC +#define XZ_DEC_IA64 +#define XZ_DEC_ARM +#define XZ_DEC_ARMTHUMB +#define XZ_DEC_SPARC +*/ + + +/* In Linux, this is used to make extern functions static when needed. */ +#ifndef XZ_EXTERN +# define XZ_EXTERN extern +#endif + +/** + * enum xz_mode - Operation mode + * + * @XZ_SINGLE: Single-call mode. This uses less RAM than + * than multi-call modes, because the LZMA2 + * dictionary doesn't need to be allocated as + * part of the decoder state. All required data + * structures are allocated at initialization, + * so xz_dec_run() cannot return XZ_MEM_ERROR. + * @XZ_PREALLOC: Multi-call mode with preallocated LZMA2 + * dictionary buffer. All data structures are + * allocated at initialization, so xz_dec_run() + * cannot return XZ_MEM_ERROR. + * @XZ_DYNALLOC: Multi-call mode. The LZMA2 dictionary is + * allocated once the required size has been + * parsed from the stream headers. If the + * allocation fails, xz_dec_run() will return + * XZ_MEM_ERROR. + * + * It is possible to enable support only for a subset of the above + * modes at compile time by defining XZ_DEC_SINGLE, XZ_DEC_PREALLOC, + * or XZ_DEC_DYNALLOC. The xz_dec kernel module is always compiled + * with support for all operation modes, but the preboot code may + * be built with fewer features to minimize code size. + */ +enum xz_mode { + XZ_SINGLE, + XZ_PREALLOC, + XZ_DYNALLOC +}; + +/** + * enum xz_ret - Return codes + * @XZ_OK: Everything is OK so far. More input or more + * output space is required to continue. This + * return code is possible only in multi-call mode + * (XZ_PREALLOC or XZ_DYNALLOC). + * @XZ_STREAM_END: Operation finished successfully. + * @XZ_UNSUPPORTED_CHECK: Integrity check type is not supported. Decoding + * is still possible in multi-call mode by simply + * calling xz_dec_run() again. + * Note that this return value is used only if + * XZ_DEC_ANY_CHECK was defined at build time, + * which is not used in the kernel. Unsupported + * check types return XZ_OPTIONS_ERROR if + * XZ_DEC_ANY_CHECK was not defined at build time. + * @XZ_MEM_ERROR: Allocating memory failed. This return code is + * possible only if the decoder was initialized + * with XZ_DYNALLOC. The amount of memory that was + * tried to be allocated was no more than the + * dict_max argument given to xz_dec_init(). + * @XZ_MEMLIMIT_ERROR: A bigger LZMA2 dictionary would be needed than + * allowed by the dict_max argument given to + * xz_dec_init(). This return value is possible + * only in multi-call mode (XZ_PREALLOC or + * XZ_DYNALLOC); the single-call mode (XZ_SINGLE) + * ignores the dict_max argument. + * @XZ_FORMAT_ERROR: File format was not recognized (wrong magic + * bytes). + * @XZ_OPTIONS_ERROR: This implementation doesn't support the requested + * compression options. In the decoder this means + * that the header CRC32 matches, but the header + * itself specifies something that we don't support. + * @XZ_DATA_ERROR: Compressed data is corrupt. + * @XZ_BUF_ERROR: Cannot make any progress. Details are slightly + * different between multi-call and single-call + * mode; more information below. + * + * In multi-call mode, XZ_BUF_ERROR is returned when two consecutive calls + * to XZ code cannot consume any input and cannot produce any new output. + * This happens when there is no new input available, or the output buffer + * is full while at least one output byte is still pending. Assuming your + * code is not buggy, you can get this error only when decoding a compressed + * stream that is truncated or otherwise corrupt. + * + * In single-call mode, XZ_BUF_ERROR is returned only when the output buffer + * is too small or the compressed input is corrupt in a way that makes the + * decoder produce more output than the caller expected. When it is + * (relatively) clear that the compressed input is truncated, XZ_DATA_ERROR + * is used instead of XZ_BUF_ERROR. + */ +enum xz_ret { + XZ_OK, + XZ_STREAM_END, + XZ_UNSUPPORTED_CHECK, + XZ_MEM_ERROR, + XZ_MEMLIMIT_ERROR, + XZ_FORMAT_ERROR, + XZ_OPTIONS_ERROR, + XZ_DATA_ERROR, + XZ_BUF_ERROR +}; + +/** + * struct xz_buf - Passing input and output buffers to XZ code + * @in: Beginning of the input buffer. This may be NULL if and only + * if in_pos is equal to in_size. + * @in_pos: Current position in the input buffer. This must not exceed + * in_size. + * @in_size: Size of the input buffer + * @out: Beginning of the output buffer. This may be NULL if and only + * if out_pos is equal to out_size. + * @out_pos: Current position in the output buffer. This must not exceed + * out_size. + * @out_size: Size of the output buffer + * + * Only the contents of the output buffer from out[out_pos] onward, and + * the variables in_pos and out_pos are modified by the XZ code. + */ +struct xz_buf { + const uint8_t *in; + size_t in_pos; + size_t in_size; + + uint8_t *out; + size_t out_pos; + size_t out_size; +}; + +/** + * struct xz_dec - Opaque type to hold the XZ decoder state + */ +struct xz_dec; + +/** + * xz_dec_init() - Allocate and initialize a XZ decoder state + * @mode: Operation mode + * @dict_max: Maximum size of the LZMA2 dictionary (history buffer) for + * multi-call decoding. This is ignored in single-call mode + * (mode == XZ_SINGLE). LZMA2 dictionary is always 2^n bytes + * or 2^n + 2^(n-1) bytes (the latter sizes are less common + * in practice), so other values for dict_max don't make sense. + * In the kernel, dictionary sizes of 64 KiB, 128 KiB, 256 KiB, + * 512 KiB, and 1 MiB are probably the only reasonable values, + * except for kernel and initramfs images where a bigger + * dictionary can be fine and useful. + * + * Single-call mode (XZ_SINGLE): xz_dec_run() decodes the whole stream at + * once. The caller must provide enough output space or the decoding will + * fail. The output space is used as the dictionary buffer, which is why + * there is no need to allocate the dictionary as part of the decoder's + * internal state. + * + * Because the output buffer is used as the workspace, streams encoded using + * a big dictionary are not a problem in single-call mode. It is enough that + * the output buffer is big enough to hold the actual uncompressed data; it + * can be smaller than the dictionary size stored in the stream headers. + * + * Multi-call mode with preallocated dictionary (XZ_PREALLOC): dict_max bytes + * of memory is preallocated for the LZMA2 dictionary. This way there is no + * risk that xz_dec_run() could run out of memory, since xz_dec_run() will + * never allocate any memory. Instead, if the preallocated dictionary is too + * small for decoding the given input stream, xz_dec_run() will return + * XZ_MEMLIMIT_ERROR. Thus, it is important to know what kind of data will be + * decoded to avoid allocating excessive amount of memory for the dictionary. + * + * Multi-call mode with dynamically allocated dictionary (XZ_DYNALLOC): + * dict_max specifies the maximum allowed dictionary size that xz_dec_run() + * may allocate once it has parsed the dictionary size from the stream + * headers. This way excessive allocations can be avoided while still + * limiting the maximum memory usage to a sane value to prevent running the + * system out of memory when decompressing streams from untrusted sources. + * + * On success, xz_dec_init() returns a pointer to struct xz_dec, which is + * ready to be used with xz_dec_run(). If memory allocation fails, + * xz_dec_init() returns NULL. + */ +XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max); + +/** + * xz_dec_run() - Run the XZ decoder + * @s: Decoder state allocated using xz_dec_init() + * @b: Input and output buffers + * + * The possible return values depend on build options and operation mode. + * See enum xz_ret for details. + * + * Note that if an error occurs in single-call mode (return value is not + * XZ_STREAM_END), b->in_pos and b->out_pos are not modified and the + * contents of the output buffer from b->out[b->out_pos] onward are + * undefined. This is true even after XZ_BUF_ERROR, because with some filter + * chains, there may be a second pass over the output buffer, and this pass + * cannot be properly done if the output buffer is truncated. Thus, you + * cannot give the single-call decoder a too small buffer and then expect to + * get that amount valid data from the beginning of the stream. You must use + * the multi-call decoder if you don't want to uncompress the whole stream. + */ +XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b); + +/** + * xz_dec_reset() - Reset an already allocated decoder state + * @s: Decoder state allocated using xz_dec_init() + * + * This function can be used to reset the multi-call decoder state without + * freeing and reallocating memory with xz_dec_end() and xz_dec_init(). + * + * In single-call mode, xz_dec_reset() is always called in the beginning of + * xz_dec_run(). Thus, explicit call to xz_dec_reset() is useful only in + * multi-call mode. + */ +XZ_EXTERN void xz_dec_reset(struct xz_dec *s); + +/** + * xz_dec_end() - Free the memory allocated for the decoder state + * @s: Decoder state allocated using xz_dec_init(). If s is NULL, + * this function does nothing. + */ +XZ_EXTERN void xz_dec_end(struct xz_dec *s); + +/* + * Standalone build (userspace build or in-kernel build for boot time use) + * needs a CRC32 implementation. For normal in-kernel use, kernel's own + * CRC32 module is used instead, and users of this module don't need to + * care about the functions below. + */ +#ifndef XZ_INTERNAL_CRC32 +# ifdef __KERNEL__ +# define XZ_INTERNAL_CRC32 0 +# else +# define XZ_INTERNAL_CRC32 1 +# endif +#endif + +/* + * If CRC64 support has been enabled with XZ_USE_CRC64, a CRC64 + * implementation is needed too. + */ +#ifndef XZ_USE_CRC64 +# undef XZ_INTERNAL_CRC64 +# define XZ_INTERNAL_CRC64 0 +#endif +#ifndef XZ_INTERNAL_CRC64 +# ifdef __KERNEL__ +# error Using CRC64 in the kernel has not been implemented. +# else +# define XZ_INTERNAL_CRC64 1 +# endif +#endif + +#if XZ_INTERNAL_CRC32 +/* + * This must be called before any other xz_* function to initialize + * the CRC32 lookup table. + */ +XZ_EXTERN void xz_crc32_init(void); + +/* + * Update CRC32 value using the polynomial from IEEE-802.3. To start a new + * calculation, the third argument must be zero. To continue the calculation, + * the previously returned value is passed as the third argument. + */ +XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc); +#endif + +#if XZ_INTERNAL_CRC64 +/* + * This must be called before any other xz_* function (except xz_crc32_init()) + * to initialize the CRC64 lookup table. + */ +XZ_EXTERN void xz_crc64_init(void); + +/* + * Update CRC64 value using the polynomial from ECMA-182. To start a new + * calculation, the third argument must be zero. To continue the calculation, + * the previously returned value is passed as the third argument. + */ +XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/depends/xz-embedded/src/xz_config.h b/depends/xz-embedded/src/xz_config.h new file mode 100644 index 00000000..eb9dac1a --- /dev/null +++ b/depends/xz-embedded/src/xz_config.h @@ -0,0 +1,124 @@ +/* + * Private includes and definitions for userspace use of XZ Embedded + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_CONFIG_H +#define XZ_CONFIG_H + +/* Uncomment to enable CRC64 support. */ +/* #define XZ_USE_CRC64 */ + +/* Uncomment as needed to enable BCJ filter decoders. */ +/* #define XZ_DEC_X86 */ +/* #define XZ_DEC_POWERPC */ +/* #define XZ_DEC_IA64 */ +/* #define XZ_DEC_ARM */ +/* #define XZ_DEC_ARMTHUMB */ +/* #define XZ_DEC_SPARC */ + +/* + * MSVC doesn't support modern C but XZ Embedded is mostly C89 + * so these are enough. + */ +#ifdef _MSC_VER +typedef unsigned char bool; +# define true 1 +# define false 0 +# define inline __inline +#else +# include <stdbool.h> +#endif + +#include <stdlib.h> +#include <string.h> + +#include "xz.h" + +#define kmalloc(size, flags) malloc(size) +#define kfree(ptr) free(ptr) +#define vmalloc(size) malloc(size) +#define vfree(ptr) free(ptr) + +#define memeq(a, b, size) (memcmp(a, b, size) == 0) +#define memzero(buf, size) memset(buf, 0, size) + +#ifndef min +# define min(x, y) ((x) < (y) ? (x) : (y)) +#endif +#define min_t(type, x, y) min(x, y) + +/* + * Some functions have been marked with __always_inline to keep the + * performance reasonable even when the compiler is optimizing for + * small code size. You may be able to save a few bytes by #defining + * __always_inline to plain inline, but don't complain if the code + * becomes slow. + * + * NOTE: System headers on GNU/Linux may #define this macro already, + * so if you want to change it, you need to #undef it first. + */ +#ifndef __always_inline +# ifdef __GNUC__ +# define __always_inline \ + inline __attribute__((__always_inline__)) +# else +# define __always_inline inline +# endif +#endif + +/* Inline functions to access unaligned unsigned 32-bit integers */ +#ifndef get_unaligned_le32 +static inline uint32_t get_unaligned_le32(const uint8_t *buf) +{ + return (uint32_t)buf[0] + | ((uint32_t)buf[1] << 8) + | ((uint32_t)buf[2] << 16) + | ((uint32_t)buf[3] << 24); +} +#endif + +#ifndef get_unaligned_be32 +static inline uint32_t get_unaligned_be32(const uint8_t *buf) +{ + return (uint32_t)(buf[0] << 24) + | ((uint32_t)buf[1] << 16) + | ((uint32_t)buf[2] << 8) + | (uint32_t)buf[3]; +} +#endif + +#ifndef put_unaligned_le32 +static inline void put_unaligned_le32(uint32_t val, uint8_t *buf) +{ + buf[0] = (uint8_t)val; + buf[1] = (uint8_t)(val >> 8); + buf[2] = (uint8_t)(val >> 16); + buf[3] = (uint8_t)(val >> 24); +} +#endif + +#ifndef put_unaligned_be32 +static inline void put_unaligned_be32(uint32_t val, uint8_t *buf) +{ + buf[0] = (uint8_t)(val >> 24); + buf[1] = (uint8_t)(val >> 16); + buf[2] = (uint8_t)(val >> 8); + buf[3] = (uint8_t)val; +} +#endif + +/* + * Use get_unaligned_le32() also for aligned access for simplicity. On + * little endian systems, #define get_le32(ptr) (*(const uint32_t *)(ptr)) + * could save a few bytes in code size. + */ +#ifndef get_le32 +# define get_le32 get_unaligned_le32 +#endif + +#endif diff --git a/depends/xz-embedded/src/xz_crc32.c b/depends/xz-embedded/src/xz_crc32.c new file mode 100644 index 00000000..34532d14 --- /dev/null +++ b/depends/xz-embedded/src/xz_crc32.c @@ -0,0 +1,59 @@ +/* + * CRC32 using the polynomial from IEEE-802.3 + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +/* + * This is not the fastest implementation, but it is pretty compact. + * The fastest versions of xz_crc32() on modern CPUs without hardware + * accelerated CRC instruction are 3-5 times as fast as this version, + * but they are bigger and use more memory for the lookup table. + */ + +#include "xz_private.h" + +/* + * STATIC_RW_DATA is used in the pre-boot environment on some architectures. + * See <linux/decompress/mm.h> for details. + */ +#ifndef STATIC_RW_DATA +# define STATIC_RW_DATA static +#endif + +STATIC_RW_DATA uint32_t xz_crc32_table[256]; + +XZ_EXTERN void xz_crc32_init(void) +{ + const uint32_t poly = 0xEDB88320; + + uint32_t i; + uint32_t j; + uint32_t r; + + for (i = 0; i < 256; ++i) { + r = i; + for (j = 0; j < 8; ++j) + r = (r >> 1) ^ (poly & ~((r & 1) - 1)); + + xz_crc32_table[i] = r; + } + + return; +} + +XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc) +{ + crc = ~crc; + + while (size != 0) { + crc = xz_crc32_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); + --size; + } + + return ~crc; +} diff --git a/depends/xz-embedded/src/xz_crc64.c b/depends/xz-embedded/src/xz_crc64.c new file mode 100644 index 00000000..ca1caee8 --- /dev/null +++ b/depends/xz-embedded/src/xz_crc64.c @@ -0,0 +1,50 @@ +/* + * CRC64 using the polynomial from ECMA-182 + * + * This file is similar to xz_crc32.c. See the comments there. + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#include "xz_private.h" + +#ifndef STATIC_RW_DATA +# define STATIC_RW_DATA static +#endif + +STATIC_RW_DATA uint64_t xz_crc64_table[256]; + +XZ_EXTERN void xz_crc64_init(void) +{ + const uint64_t poly = 0xC96C5795D7870F42; + + uint32_t i; + uint32_t j; + uint64_t r; + + for (i = 0; i < 256; ++i) { + r = i; + for (j = 0; j < 8; ++j) + r = (r >> 1) ^ (poly & ~((r & 1) - 1)); + + xz_crc64_table[i] = r; + } + + return; +} + +XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc) +{ + crc = ~crc; + + while (size != 0) { + crc = xz_crc64_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); + --size; + } + + return ~crc; +} diff --git a/depends/xz-embedded/src/xz_dec_bcj.c b/depends/xz-embedded/src/xz_dec_bcj.c new file mode 100644 index 00000000..a768e6d2 --- /dev/null +++ b/depends/xz-embedded/src/xz_dec_bcj.c @@ -0,0 +1,574 @@ +/* + * Branch/Call/Jump (BCJ) filter decoders + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#include "xz_private.h" + +/* + * The rest of the file is inside this ifdef. It makes things a little more + * convenient when building without support for any BCJ filters. + */ +#ifdef XZ_DEC_BCJ + +struct xz_dec_bcj { + /* Type of the BCJ filter being used */ + enum { + BCJ_X86 = 4, /* x86 or x86-64 */ + BCJ_POWERPC = 5, /* Big endian only */ + BCJ_IA64 = 6, /* Big or little endian */ + BCJ_ARM = 7, /* Little endian only */ + BCJ_ARMTHUMB = 8, /* Little endian only */ + BCJ_SPARC = 9 /* Big or little endian */ + } type; + + /* + * Return value of the next filter in the chain. We need to preserve + * this information across calls, because we must not call the next + * filter anymore once it has returned XZ_STREAM_END. + */ + enum xz_ret ret; + + /* True if we are operating in single-call mode. */ + bool single_call; + + /* + * Absolute position relative to the beginning of the uncompressed + * data (in a single .xz Block). We care only about the lowest 32 + * bits so this doesn't need to be uint64_t even with big files. + */ + uint32_t pos; + + /* x86 filter state */ + uint32_t x86_prev_mask; + + /* Temporary space to hold the variables from struct xz_buf */ + uint8_t *out; + size_t out_pos; + size_t out_size; + + struct { + /* Amount of already filtered data in the beginning of buf */ + size_t filtered; + + /* Total amount of data currently stored in buf */ + size_t size; + + /* + * Buffer to hold a mix of filtered and unfiltered data. This + * needs to be big enough to hold Alignment + 2 * Look-ahead: + * + * Type Alignment Look-ahead + * x86 1 4 + * PowerPC 4 0 + * IA-64 16 0 + * ARM 4 0 + * ARM-Thumb 2 2 + * SPARC 4 0 + */ + uint8_t buf[16]; + } temp; +}; + +#ifdef XZ_DEC_X86 +/* + * This is used to test the most significant byte of a memory address + * in an x86 instruction. + */ +static inline int bcj_x86_test_msbyte(uint8_t b) +{ + return b == 0x00 || b == 0xFF; +} + +static size_t bcj_x86(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + static const bool mask_to_allowed_status[8] + = { true, true, true, false, true, false, false, false }; + + static const uint8_t mask_to_bit_num[8] = { 0, 1, 2, 2, 3, 3, 3, 3 }; + + size_t i; + size_t prev_pos = (size_t)-1; + uint32_t prev_mask = s->x86_prev_mask; + uint32_t src; + uint32_t dest; + uint32_t j; + uint8_t b; + + if (size <= 4) + return 0; + + size -= 4; + for (i = 0; i < size; ++i) { + if ((buf[i] & 0xFE) != 0xE8) + continue; + + prev_pos = i - prev_pos; + if (prev_pos > 3) { + prev_mask = 0; + } else { + prev_mask = (prev_mask << (prev_pos - 1)) & 7; + if (prev_mask != 0) { + b = buf[i + 4 - mask_to_bit_num[prev_mask]]; + if (!mask_to_allowed_status[prev_mask] + || bcj_x86_test_msbyte(b)) { + prev_pos = i; + prev_mask = (prev_mask << 1) | 1; + continue; + } + } + } + + prev_pos = i; + + if (bcj_x86_test_msbyte(buf[i + 4])) { + src = get_unaligned_le32(buf + i + 1); + while (true) { + dest = src - (s->pos + (uint32_t)i + 5); + if (prev_mask == 0) + break; + + j = mask_to_bit_num[prev_mask] * 8; + b = (uint8_t)(dest >> (24 - j)); + if (!bcj_x86_test_msbyte(b)) + break; + + src = dest ^ (((uint32_t)1 << (32 - j)) - 1); + } + + dest &= 0x01FFFFFF; + dest |= (uint32_t)0 - (dest & 0x01000000); + put_unaligned_le32(dest, buf + i + 1); + i += 4; + } else { + prev_mask = (prev_mask << 1) | 1; + } + } + + prev_pos = i - prev_pos; + s->x86_prev_mask = prev_pos > 3 ? 0 : prev_mask << (prev_pos - 1); + return i; +} +#endif + +#ifdef XZ_DEC_POWERPC +static size_t bcj_powerpc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + size_t i; + uint32_t instr; + + for (i = 0; i + 4 <= size; i += 4) { + instr = get_unaligned_be32(buf + i); + if ((instr & 0xFC000003) == 0x48000001) { + instr &= 0x03FFFFFC; + instr -= s->pos + (uint32_t)i; + instr &= 0x03FFFFFC; + instr |= 0x48000001; + put_unaligned_be32(instr, buf + i); + } + } + + return i; +} +#endif + +#ifdef XZ_DEC_IA64 +static size_t bcj_ia64(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + static const uint8_t branch_table[32] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 4, 4, 6, 6, 0, 0, 7, 7, + 4, 4, 0, 0, 4, 4, 0, 0 + }; + + /* + * The local variables take a little bit stack space, but it's less + * than what LZMA2 decoder takes, so it doesn't make sense to reduce + * stack usage here without doing that for the LZMA2 decoder too. + */ + + /* Loop counters */ + size_t i; + size_t j; + + /* Instruction slot (0, 1, or 2) in the 128-bit instruction word */ + uint32_t slot; + + /* Bitwise offset of the instruction indicated by slot */ + uint32_t bit_pos; + + /* bit_pos split into byte and bit parts */ + uint32_t byte_pos; + uint32_t bit_res; + + /* Address part of an instruction */ + uint32_t addr; + + /* Mask used to detect which instructions to convert */ + uint32_t mask; + + /* 41-bit instruction stored somewhere in the lowest 48 bits */ + uint64_t instr; + + /* Instruction normalized with bit_res for easier manipulation */ + uint64_t norm; + + for (i = 0; i + 16 <= size; i += 16) { + mask = branch_table[buf[i] & 0x1F]; + for (slot = 0, bit_pos = 5; slot < 3; ++slot, bit_pos += 41) { + if (((mask >> slot) & 1) == 0) + continue; + + byte_pos = bit_pos >> 3; + bit_res = bit_pos & 7; + instr = 0; + for (j = 0; j < 6; ++j) + instr |= (uint64_t)(buf[i + j + byte_pos]) + << (8 * j); + + norm = instr >> bit_res; + + if (((norm >> 37) & 0x0F) == 0x05 + && ((norm >> 9) & 0x07) == 0) { + addr = (norm >> 13) & 0x0FFFFF; + addr |= ((uint32_t)(norm >> 36) & 1) << 20; + addr <<= 4; + addr -= s->pos + (uint32_t)i; + addr >>= 4; + + norm &= ~((uint64_t)0x8FFFFF << 13); + norm |= (uint64_t)(addr & 0x0FFFFF) << 13; + norm |= (uint64_t)(addr & 0x100000) + << (36 - 20); + + instr &= (1 << bit_res) - 1; + instr |= norm << bit_res; + + for (j = 0; j < 6; j++) + buf[i + j + byte_pos] + = (uint8_t)(instr >> (8 * j)); + } + } + } + + return i; +} +#endif + +#ifdef XZ_DEC_ARM +static size_t bcj_arm(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + size_t i; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 4) { + if (buf[i + 3] == 0xEB) { + addr = (uint32_t)buf[i] | ((uint32_t)buf[i + 1] << 8) + | ((uint32_t)buf[i + 2] << 16); + addr <<= 2; + addr -= s->pos + (uint32_t)i + 8; + addr >>= 2; + buf[i] = (uint8_t)addr; + buf[i + 1] = (uint8_t)(addr >> 8); + buf[i + 2] = (uint8_t)(addr >> 16); + } + } + + return i; +} +#endif + +#ifdef XZ_DEC_ARMTHUMB +static size_t bcj_armthumb(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + size_t i; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 2) { + if ((buf[i + 1] & 0xF8) == 0xF0 + && (buf[i + 3] & 0xF8) == 0xF8) { + addr = (((uint32_t)buf[i + 1] & 0x07) << 19) + | ((uint32_t)buf[i] << 11) + | (((uint32_t)buf[i + 3] & 0x07) << 8) + | (uint32_t)buf[i + 2]; + addr <<= 1; + addr -= s->pos + (uint32_t)i + 4; + addr >>= 1; + buf[i + 1] = (uint8_t)(0xF0 | ((addr >> 19) & 0x07)); + buf[i] = (uint8_t)(addr >> 11); + buf[i + 3] = (uint8_t)(0xF8 | ((addr >> 8) & 0x07)); + buf[i + 2] = (uint8_t)addr; + i += 2; + } + } + + return i; +} +#endif + +#ifdef XZ_DEC_SPARC +static size_t bcj_sparc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) +{ + size_t i; + uint32_t instr; + + for (i = 0; i + 4 <= size; i += 4) { + instr = get_unaligned_be32(buf + i); + if ((instr >> 22) == 0x100 || (instr >> 22) == 0x1FF) { + instr <<= 2; + instr -= s->pos + (uint32_t)i; + instr >>= 2; + instr = ((uint32_t)0x40000000 - (instr & 0x400000)) + | 0x40000000 | (instr & 0x3FFFFF); + put_unaligned_be32(instr, buf + i); + } + } + + return i; +} +#endif + +/* + * Apply the selected BCJ filter. Update *pos and s->pos to match the amount + * of data that got filtered. + * + * NOTE: This is implemented as a switch statement to avoid using function + * pointers, which could be problematic in the kernel boot code, which must + * avoid pointers to static data (at least on x86). + */ +static void bcj_apply(struct xz_dec_bcj *s, + uint8_t *buf, size_t *pos, size_t size) +{ + size_t filtered; + + buf += *pos; + size -= *pos; + + switch (s->type) { +#ifdef XZ_DEC_X86 + case BCJ_X86: + filtered = bcj_x86(s, buf, size); + break; +#endif +#ifdef XZ_DEC_POWERPC + case BCJ_POWERPC: + filtered = bcj_powerpc(s, buf, size); + break; +#endif +#ifdef XZ_DEC_IA64 + case BCJ_IA64: + filtered = bcj_ia64(s, buf, size); + break; +#endif +#ifdef XZ_DEC_ARM + case BCJ_ARM: + filtered = bcj_arm(s, buf, size); + break; +#endif +#ifdef XZ_DEC_ARMTHUMB + case BCJ_ARMTHUMB: + filtered = bcj_armthumb(s, buf, size); + break; +#endif +#ifdef XZ_DEC_SPARC + case BCJ_SPARC: + filtered = bcj_sparc(s, buf, size); + break; +#endif + default: + /* Never reached but silence compiler warnings. */ + filtered = 0; + break; + } + + *pos += filtered; + s->pos += filtered; +} + +/* + * Flush pending filtered data from temp to the output buffer. + * Move the remaining mixture of possibly filtered and unfiltered + * data to the beginning of temp. + */ +static void bcj_flush(struct xz_dec_bcj *s, struct xz_buf *b) +{ + size_t copy_size; + + copy_size = min_t(size_t, s->temp.filtered, b->out_size - b->out_pos); + memcpy(b->out + b->out_pos, s->temp.buf, copy_size); + b->out_pos += copy_size; + + s->temp.filtered -= copy_size; + s->temp.size -= copy_size; + memmove(s->temp.buf, s->temp.buf + copy_size, s->temp.size); +} + +/* + * The BCJ filter functions are primitive in sense that they process the + * data in chunks of 1-16 bytes. To hide this issue, this function does + * some buffering. + */ +XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, + struct xz_dec_lzma2 *lzma2, + struct xz_buf *b) +{ + size_t out_start; + + /* + * Flush pending already filtered data to the output buffer. Return + * immediatelly if we couldn't flush everything, or if the next + * filter in the chain had already returned XZ_STREAM_END. + */ + if (s->temp.filtered > 0) { + bcj_flush(s, b); + if (s->temp.filtered > 0) + return XZ_OK; + + if (s->ret == XZ_STREAM_END) + return XZ_STREAM_END; + } + + /* + * If we have more output space than what is currently pending in + * temp, copy the unfiltered data from temp to the output buffer + * and try to fill the output buffer by decoding more data from the + * next filter in the chain. Apply the BCJ filter on the new data + * in the output buffer. If everything cannot be filtered, copy it + * to temp and rewind the output buffer position accordingly. + * + * This needs to be always run when temp.size == 0 to handle a special + * case where the output buffer is full and the next filter has no + * more output coming but hasn't returned XZ_STREAM_END yet. + */ + if (s->temp.size < b->out_size - b->out_pos || s->temp.size == 0) { + out_start = b->out_pos; + memcpy(b->out + b->out_pos, s->temp.buf, s->temp.size); + b->out_pos += s->temp.size; + + s->ret = xz_dec_lzma2_run(lzma2, b); + if (s->ret != XZ_STREAM_END + && (s->ret != XZ_OK || s->single_call)) + return s->ret; + + bcj_apply(s, b->out, &out_start, b->out_pos); + + /* + * As an exception, if the next filter returned XZ_STREAM_END, + * we can do that too, since the last few bytes that remain + * unfiltered are meant to remain unfiltered. + */ + if (s->ret == XZ_STREAM_END) + return XZ_STREAM_END; + + s->temp.size = b->out_pos - out_start; + b->out_pos -= s->temp.size; + memcpy(s->temp.buf, b->out + b->out_pos, s->temp.size); + + /* + * If there wasn't enough input to the next filter to fill + * the output buffer with unfiltered data, there's no point + * to try decoding more data to temp. + */ + if (b->out_pos + s->temp.size < b->out_size) + return XZ_OK; + } + + /* + * We have unfiltered data in temp. If the output buffer isn't full + * yet, try to fill the temp buffer by decoding more data from the + * next filter. Apply the BCJ filter on temp. Then we hopefully can + * fill the actual output buffer by copying filtered data from temp. + * A mix of filtered and unfiltered data may be left in temp; it will + * be taken care on the next call to this function. + */ + if (b->out_pos < b->out_size) { + /* Make b->out{,_pos,_size} temporarily point to s->temp. */ + s->out = b->out; + s->out_pos = b->out_pos; + s->out_size = b->out_size; + b->out = s->temp.buf; + b->out_pos = s->temp.size; + b->out_size = sizeof(s->temp.buf); + + s->ret = xz_dec_lzma2_run(lzma2, b); + + s->temp.size = b->out_pos; + b->out = s->out; + b->out_pos = s->out_pos; + b->out_size = s->out_size; + + if (s->ret != XZ_OK && s->ret != XZ_STREAM_END) + return s->ret; + + bcj_apply(s, s->temp.buf, &s->temp.filtered, s->temp.size); + + /* + * If the next filter returned XZ_STREAM_END, we mark that + * everything is filtered, since the last unfiltered bytes + * of the stream are meant to be left as is. + */ + if (s->ret == XZ_STREAM_END) + s->temp.filtered = s->temp.size; + + bcj_flush(s, b); + if (s->temp.filtered > 0) + return XZ_OK; + } + + return s->ret; +} + +XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call) +{ + struct xz_dec_bcj *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s != NULL) + s->single_call = single_call; + + return s; +} + +XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id) +{ + switch (id) { +#ifdef XZ_DEC_X86 + case BCJ_X86: +#endif +#ifdef XZ_DEC_POWERPC + case BCJ_POWERPC: +#endif +#ifdef XZ_DEC_IA64 + case BCJ_IA64: +#endif +#ifdef XZ_DEC_ARM + case BCJ_ARM: +#endif +#ifdef XZ_DEC_ARMTHUMB + case BCJ_ARMTHUMB: +#endif +#ifdef XZ_DEC_SPARC + case BCJ_SPARC: +#endif + break; + + default: + /* Unsupported Filter ID */ + return XZ_OPTIONS_ERROR; + } + + s->type = id; + s->ret = XZ_OK; + s->pos = 0; + s->x86_prev_mask = 0; + s->temp.filtered = 0; + s->temp.size = 0; + + return XZ_OK; +} + +#endif diff --git a/depends/xz-embedded/src/xz_dec_lzma2.c b/depends/xz-embedded/src/xz_dec_lzma2.c new file mode 100644 index 00000000..a6cdc969 --- /dev/null +++ b/depends/xz-embedded/src/xz_dec_lzma2.c @@ -0,0 +1,1171 @@ +/* + * LZMA2 decoder + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#include "xz_private.h" +#include "xz_lzma2.h" + +/* + * Range decoder initialization eats the first five bytes of each LZMA chunk. + */ +#define RC_INIT_BYTES 5 + +/* + * Minimum number of usable input buffer to safely decode one LZMA symbol. + * The worst case is that we decode 22 bits using probabilities and 26 + * direct bits. This may decode at maximum of 20 bytes of input. However, + * lzma_main() does an extra normalization before returning, thus we + * need to put 21 here. + */ +#define LZMA_IN_REQUIRED 21 + +/* + * Dictionary (history buffer) + * + * These are always true: + * start <= pos <= full <= end + * pos <= limit <= end + * + * In multi-call mode, also these are true: + * end == size + * size <= size_max + * allocated <= size + * + * Most of these variables are size_t to support single-call mode, + * in which the dictionary variables address the actual output + * buffer directly. + */ +struct dictionary { + /* Beginning of the history buffer */ + uint8_t *buf; + + /* Old position in buf (before decoding more data) */ + size_t start; + + /* Position in buf */ + size_t pos; + + /* + * How full dictionary is. This is used to detect corrupt input that + * would read beyond the beginning of the uncompressed stream. + */ + size_t full; + + /* Write limit; we don't write to buf[limit] or later bytes. */ + size_t limit; + + /* + * End of the dictionary buffer. In multi-call mode, this is + * the same as the dictionary size. In single-call mode, this + * indicates the size of the output buffer. + */ + size_t end; + + /* + * Size of the dictionary as specified in Block Header. This is used + * together with "full" to detect corrupt input that would make us + * read beyond the beginning of the uncompressed stream. + */ + uint32_t size; + + /* + * Maximum allowed dictionary size in multi-call mode. + * This is ignored in single-call mode. + */ + uint32_t size_max; + + /* + * Amount of memory currently allocated for the dictionary. + * This is used only with XZ_DYNALLOC. (With XZ_PREALLOC, + * size_max is always the same as the allocated size.) + */ + uint32_t allocated; + + /* Operation mode */ + enum xz_mode mode; +}; + +/* Range decoder */ +struct rc_dec { + uint32_t range; + uint32_t code; + + /* + * Number of initializing bytes remaining to be read + * by rc_read_init(). + */ + uint32_t init_bytes_left; + + /* + * Buffer from which we read our input. It can be either + * temp.buf or the caller-provided input buffer. + */ + const uint8_t *in; + size_t in_pos; + size_t in_limit; +}; + +/* Probabilities for a length decoder. */ +struct lzma_len_dec { + /* Probability of match length being at least 10 */ + uint16_t choice; + + /* Probability of match length being at least 18 */ + uint16_t choice2; + + /* Probabilities for match lengths 2-9 */ + uint16_t low[POS_STATES_MAX][LEN_LOW_SYMBOLS]; + + /* Probabilities for match lengths 10-17 */ + uint16_t mid[POS_STATES_MAX][LEN_MID_SYMBOLS]; + + /* Probabilities for match lengths 18-273 */ + uint16_t high[LEN_HIGH_SYMBOLS]; +}; + +struct lzma_dec { + /* Distances of latest four matches */ + uint32_t rep0; + uint32_t rep1; + uint32_t rep2; + uint32_t rep3; + + /* Types of the most recently seen LZMA symbols */ + enum lzma_state state; + + /* + * Length of a match. This is updated so that dict_repeat can + * be called again to finish repeating the whole match. + */ + uint32_t len; + + /* + * LZMA properties or related bit masks (number of literal + * context bits, a mask dervied from the number of literal + * position bits, and a mask dervied from the number + * position bits) + */ + uint32_t lc; + uint32_t literal_pos_mask; /* (1 << lp) - 1 */ + uint32_t pos_mask; /* (1 << pb) - 1 */ + + /* If 1, it's a match. Otherwise it's a single 8-bit literal. */ + uint16_t is_match[STATES][POS_STATES_MAX]; + + /* If 1, it's a repeated match. The distance is one of rep0 .. rep3. */ + uint16_t is_rep[STATES]; + + /* + * If 0, distance of a repeated match is rep0. + * Otherwise check is_rep1. + */ + uint16_t is_rep0[STATES]; + + /* + * If 0, distance of a repeated match is rep1. + * Otherwise check is_rep2. + */ + uint16_t is_rep1[STATES]; + + /* If 0, distance of a repeated match is rep2. Otherwise it is rep3. */ + uint16_t is_rep2[STATES]; + + /* + * If 1, the repeated match has length of one byte. Otherwise + * the length is decoded from rep_len_decoder. + */ + uint16_t is_rep0_long[STATES][POS_STATES_MAX]; + + /* + * Probability tree for the highest two bits of the match + * distance. There is a separate probability tree for match + * lengths of 2 (i.e. MATCH_LEN_MIN), 3, 4, and [5, 273]. + */ + uint16_t dist_slot[DIST_STATES][DIST_SLOTS]; + + /* + * Probility trees for additional bits for match distance + * when the distance is in the range [4, 127]. + */ + uint16_t dist_special[FULL_DISTANCES - DIST_MODEL_END]; + + /* + * Probability tree for the lowest four bits of a match + * distance that is equal to or greater than 128. + */ + uint16_t dist_align[ALIGN_SIZE]; + + /* Length of a normal match */ + struct lzma_len_dec match_len_dec; + + /* Length of a repeated match */ + struct lzma_len_dec rep_len_dec; + + /* Probabilities of literals */ + uint16_t literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE]; +}; + +struct lzma2_dec { + /* Position in xz_dec_lzma2_run(). */ + enum lzma2_seq { + SEQ_CONTROL, + SEQ_UNCOMPRESSED_1, + SEQ_UNCOMPRESSED_2, + SEQ_COMPRESSED_0, + SEQ_COMPRESSED_1, + SEQ_PROPERTIES, + SEQ_LZMA_PREPARE, + SEQ_LZMA_RUN, + SEQ_COPY + } sequence; + + /* Next position after decoding the compressed size of the chunk. */ + enum lzma2_seq next_sequence; + + /* Uncompressed size of LZMA chunk (2 MiB at maximum) */ + uint32_t uncompressed; + + /* + * Compressed size of LZMA chunk or compressed/uncompressed + * size of uncompressed chunk (64 KiB at maximum) + */ + uint32_t compressed; + + /* + * True if dictionary reset is needed. This is false before + * the first chunk (LZMA or uncompressed). + */ + bool need_dict_reset; + + /* + * True if new LZMA properties are needed. This is false + * before the first LZMA chunk. + */ + bool need_props; +}; + +struct xz_dec_lzma2 { + /* + * The order below is important on x86 to reduce code size and + * it shouldn't hurt on other platforms. Everything up to and + * including lzma.pos_mask are in the first 128 bytes on x86-32, + * which allows using smaller instructions to access those + * variables. On x86-64, fewer variables fit into the first 128 + * bytes, but this is still the best order without sacrificing + * the readability by splitting the structures. + */ + struct rc_dec rc; + struct dictionary dict; + struct lzma2_dec lzma2; + struct lzma_dec lzma; + + /* + * Temporary buffer which holds small number of input bytes between + * decoder calls. See lzma2_lzma() for details. + */ + struct { + uint32_t size; + uint8_t buf[3 * LZMA_IN_REQUIRED]; + } temp; +}; + +/************** + * Dictionary * + **************/ + +/* + * Reset the dictionary state. When in single-call mode, set up the beginning + * of the dictionary to point to the actual output buffer. + */ +static void dict_reset(struct dictionary *dict, struct xz_buf *b) +{ + if (DEC_IS_SINGLE(dict->mode)) { + dict->buf = b->out + b->out_pos; + dict->end = b->out_size - b->out_pos; + } + + dict->start = 0; + dict->pos = 0; + dict->limit = 0; + dict->full = 0; +} + +/* Set dictionary write limit */ +static void dict_limit(struct dictionary *dict, size_t out_max) +{ + if (dict->end - dict->pos <= out_max) + dict->limit = dict->end; + else + dict->limit = dict->pos + out_max; +} + +/* Return true if at least one byte can be written into the dictionary. */ +static inline bool dict_has_space(const struct dictionary *dict) +{ + return dict->pos < dict->limit; +} + +/* + * Get a byte from the dictionary at the given distance. The distance is + * assumed to valid, or as a special case, zero when the dictionary is + * still empty. This special case is needed for single-call decoding to + * avoid writing a '\0' to the end of the destination buffer. + */ +static inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist) +{ + size_t offset = dict->pos - dist - 1; + + if (dist >= dict->pos) + offset += dict->end; + + return dict->full > 0 ? dict->buf[offset] : 0; +} + +/* + * Put one byte into the dictionary. It is assumed that there is space for it. + */ +static inline void dict_put(struct dictionary *dict, uint8_t byte) +{ + dict->buf[dict->pos++] = byte; + + if (dict->full < dict->pos) + dict->full = dict->pos; +} + +/* + * Repeat given number of bytes from the given distance. If the distance is + * invalid, false is returned. On success, true is returned and *len is + * updated to indicate how many bytes were left to be repeated. + */ +static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) +{ + size_t back; + uint32_t left; + + if (dist >= dict->full || dist >= dict->size) + return false; + + left = min_t(size_t, dict->limit - dict->pos, *len); + *len -= left; + + back = dict->pos - dist - 1; + if (dist >= dict->pos) + back += dict->end; + + do { + dict->buf[dict->pos++] = dict->buf[back++]; + if (back == dict->end) + back = 0; + } while (--left > 0); + + if (dict->full < dict->pos) + dict->full = dict->pos; + + return true; +} + +/* Copy uncompressed data as is from input to dictionary and output buffers. */ +static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, + uint32_t *left) +{ + size_t copy_size; + + while (*left > 0 && b->in_pos < b->in_size + && b->out_pos < b->out_size) { + copy_size = min(b->in_size - b->in_pos, + b->out_size - b->out_pos); + if (copy_size > dict->end - dict->pos) + copy_size = dict->end - dict->pos; + if (copy_size > *left) + copy_size = *left; + + *left -= copy_size; + + memcpy(dict->buf + dict->pos, b->in + b->in_pos, copy_size); + dict->pos += copy_size; + + if (dict->full < dict->pos) + dict->full = dict->pos; + + if (DEC_IS_MULTI(dict->mode)) { + if (dict->pos == dict->end) + dict->pos = 0; + + memcpy(b->out + b->out_pos, b->in + b->in_pos, + copy_size); + } + + dict->start = dict->pos; + + b->out_pos += copy_size; + b->in_pos += copy_size; + } +} + +/* + * Flush pending data from dictionary to b->out. It is assumed that there is + * enough space in b->out. This is guaranteed because caller uses dict_limit() + * before decoding data into the dictionary. + */ +static uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b) +{ + size_t copy_size = dict->pos - dict->start; + + if (DEC_IS_MULTI(dict->mode)) { + if (dict->pos == dict->end) + dict->pos = 0; + + memcpy(b->out + b->out_pos, dict->buf + dict->start, + copy_size); + } + + dict->start = dict->pos; + b->out_pos += copy_size; + return copy_size; +} + +/***************** + * Range decoder * + *****************/ + +/* Reset the range decoder. */ +static void rc_reset(struct rc_dec *rc) +{ + rc->range = (uint32_t)-1; + rc->code = 0; + rc->init_bytes_left = RC_INIT_BYTES; +} + +/* + * Read the first five initial bytes into rc->code if they haven't been + * read already. (Yes, the first byte gets completely ignored.) + */ +static bool rc_read_init(struct rc_dec *rc, struct xz_buf *b) +{ + while (rc->init_bytes_left > 0) { + if (b->in_pos == b->in_size) + return false; + + rc->code = (rc->code << 8) + b->in[b->in_pos++]; + --rc->init_bytes_left; + } + + return true; +} + +/* Return true if there may not be enough input for the next decoding loop. */ +static inline bool rc_limit_exceeded(const struct rc_dec *rc) +{ + return rc->in_pos > rc->in_limit; +} + +/* + * Return true if it is possible (from point of view of range decoder) that + * we have reached the end of the LZMA chunk. + */ +static inline bool rc_is_finished(const struct rc_dec *rc) +{ + return rc->code == 0; +} + +/* Read the next input byte if needed. */ +static __always_inline void rc_normalize(struct rc_dec *rc) +{ + if (rc->range < RC_TOP_VALUE) { + rc->range <<= RC_SHIFT_BITS; + rc->code = (rc->code << RC_SHIFT_BITS) + rc->in[rc->in_pos++]; + } +} + +/* + * Decode one bit. In some versions, this function has been splitted in three + * functions so that the compiler is supposed to be able to more easily avoid + * an extra branch. In this particular version of the LZMA decoder, this + * doesn't seem to be a good idea (tested with GCC 3.3.6, 3.4.6, and 4.3.3 + * on x86). Using a non-splitted version results in nicer looking code too. + * + * NOTE: This must return an int. Do not make it return a bool or the speed + * of the code generated by GCC 3.x decreases 10-15 %. (GCC 4.3 doesn't care, + * and it generates 10-20 % faster code than GCC 3.x from this file anyway.) + */ +static __always_inline int rc_bit(struct rc_dec *rc, uint16_t *prob) +{ + uint32_t bound; + int bit; + + rc_normalize(rc); + bound = (rc->range >> RC_BIT_MODEL_TOTAL_BITS) * *prob; + if (rc->code < bound) { + rc->range = bound; + *prob += (RC_BIT_MODEL_TOTAL - *prob) >> RC_MOVE_BITS; + bit = 0; + } else { + rc->range -= bound; + rc->code -= bound; + *prob -= *prob >> RC_MOVE_BITS; + bit = 1; + } + + return bit; +} + +/* Decode a bittree starting from the most significant bit. */ +static __always_inline uint32_t rc_bittree(struct rc_dec *rc, + uint16_t *probs, uint32_t limit) +{ + uint32_t symbol = 1; + + do { + if (rc_bit(rc, &probs[symbol])) + symbol = (symbol << 1) + 1; + else + symbol <<= 1; + } while (symbol < limit); + + return symbol; +} + +/* Decode a bittree starting from the least significant bit. */ +static __always_inline void rc_bittree_reverse(struct rc_dec *rc, + uint16_t *probs, + uint32_t *dest, uint32_t limit) +{ + uint32_t symbol = 1; + uint32_t i = 0; + + do { + if (rc_bit(rc, &probs[symbol])) { + symbol = (symbol << 1) + 1; + *dest += 1 << i; + } else { + symbol <<= 1; + } + } while (++i < limit); +} + +/* Decode direct bits (fixed fifty-fifty probability) */ +static inline void rc_direct(struct rc_dec *rc, uint32_t *dest, uint32_t limit) +{ + uint32_t mask; + + do { + rc_normalize(rc); + rc->range >>= 1; + rc->code -= rc->range; + mask = (uint32_t)0 - (rc->code >> 31); + rc->code += rc->range & mask; + *dest = (*dest << 1) + (mask + 1); + } while (--limit > 0); +} + +/******** + * LZMA * + ********/ + +/* Get pointer to literal coder probability array. */ +static uint16_t *lzma_literal_probs(struct xz_dec_lzma2 *s) +{ + uint32_t prev_byte = dict_get(&s->dict, 0); + uint32_t low = prev_byte >> (8 - s->lzma.lc); + uint32_t high = (s->dict.pos & s->lzma.literal_pos_mask) << s->lzma.lc; + return s->lzma.literal[low + high]; +} + +/* Decode a literal (one 8-bit byte) */ +static void lzma_literal(struct xz_dec_lzma2 *s) +{ + uint16_t *probs; + uint32_t symbol; + uint32_t match_byte; + uint32_t match_bit; + uint32_t offset; + uint32_t i; + + probs = lzma_literal_probs(s); + + if (lzma_state_is_literal(s->lzma.state)) { + symbol = rc_bittree(&s->rc, probs, 0x100); + } else { + symbol = 1; + match_byte = dict_get(&s->dict, s->lzma.rep0) << 1; + offset = 0x100; + + do { + match_bit = match_byte & offset; + match_byte <<= 1; + i = offset + match_bit + symbol; + + if (rc_bit(&s->rc, &probs[i])) { + symbol = (symbol << 1) + 1; + offset &= match_bit; + } else { + symbol <<= 1; + offset &= ~match_bit; + } + } while (symbol < 0x100); + } + + dict_put(&s->dict, (uint8_t)symbol); + lzma_state_literal(&s->lzma.state); +} + +/* Decode the length of the match into s->lzma.len. */ +static void lzma_len(struct xz_dec_lzma2 *s, struct lzma_len_dec *l, + uint32_t pos_state) +{ + uint16_t *probs; + uint32_t limit; + + if (!rc_bit(&s->rc, &l->choice)) { + probs = l->low[pos_state]; + limit = LEN_LOW_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN; + } else { + if (!rc_bit(&s->rc, &l->choice2)) { + probs = l->mid[pos_state]; + limit = LEN_MID_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS; + } else { + probs = l->high; + limit = LEN_HIGH_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS + + LEN_MID_SYMBOLS; + } + } + + s->lzma.len += rc_bittree(&s->rc, probs, limit) - limit; +} + +/* Decode a match. The distance will be stored in s->lzma.rep0. */ +static void lzma_match(struct xz_dec_lzma2 *s, uint32_t pos_state) +{ + uint16_t *probs; + uint32_t dist_slot; + uint32_t limit; + + lzma_state_match(&s->lzma.state); + + s->lzma.rep3 = s->lzma.rep2; + s->lzma.rep2 = s->lzma.rep1; + s->lzma.rep1 = s->lzma.rep0; + + lzma_len(s, &s->lzma.match_len_dec, pos_state); + + probs = s->lzma.dist_slot[lzma_get_dist_state(s->lzma.len)]; + dist_slot = rc_bittree(&s->rc, probs, DIST_SLOTS) - DIST_SLOTS; + + if (dist_slot < DIST_MODEL_START) { + s->lzma.rep0 = dist_slot; + } else { + limit = (dist_slot >> 1) - 1; + s->lzma.rep0 = 2 + (dist_slot & 1); + + if (dist_slot < DIST_MODEL_END) { + s->lzma.rep0 <<= limit; + probs = s->lzma.dist_special + s->lzma.rep0 + - dist_slot - 1; + rc_bittree_reverse(&s->rc, probs, + &s->lzma.rep0, limit); + } else { + rc_direct(&s->rc, &s->lzma.rep0, limit - ALIGN_BITS); + s->lzma.rep0 <<= ALIGN_BITS; + rc_bittree_reverse(&s->rc, s->lzma.dist_align, + &s->lzma.rep0, ALIGN_BITS); + } + } +} + +/* + * Decode a repeated match. The distance is one of the four most recently + * seen matches. The distance will be stored in s->lzma.rep0. + */ +static void lzma_rep_match(struct xz_dec_lzma2 *s, uint32_t pos_state) +{ + uint32_t tmp; + + if (!rc_bit(&s->rc, &s->lzma.is_rep0[s->lzma.state])) { + if (!rc_bit(&s->rc, &s->lzma.is_rep0_long[ + s->lzma.state][pos_state])) { + lzma_state_short_rep(&s->lzma.state); + s->lzma.len = 1; + return; + } + } else { + if (!rc_bit(&s->rc, &s->lzma.is_rep1[s->lzma.state])) { + tmp = s->lzma.rep1; + } else { + if (!rc_bit(&s->rc, &s->lzma.is_rep2[s->lzma.state])) { + tmp = s->lzma.rep2; + } else { + tmp = s->lzma.rep3; + s->lzma.rep3 = s->lzma.rep2; + } + + s->lzma.rep2 = s->lzma.rep1; + } + + s->lzma.rep1 = s->lzma.rep0; + s->lzma.rep0 = tmp; + } + + lzma_state_long_rep(&s->lzma.state); + lzma_len(s, &s->lzma.rep_len_dec, pos_state); +} + +/* LZMA decoder core */ +static bool lzma_main(struct xz_dec_lzma2 *s) +{ + uint32_t pos_state; + + /* + * If the dictionary was reached during the previous call, try to + * finish the possibly pending repeat in the dictionary. + */ + if (dict_has_space(&s->dict) && s->lzma.len > 0) + dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0); + + /* + * Decode more LZMA symbols. One iteration may consume up to + * LZMA_IN_REQUIRED - 1 bytes. + */ + while (dict_has_space(&s->dict) && !rc_limit_exceeded(&s->rc)) { + pos_state = s->dict.pos & s->lzma.pos_mask; + + if (!rc_bit(&s->rc, &s->lzma.is_match[ + s->lzma.state][pos_state])) { + lzma_literal(s); + } else { + if (rc_bit(&s->rc, &s->lzma.is_rep[s->lzma.state])) + lzma_rep_match(s, pos_state); + else + lzma_match(s, pos_state); + + if (!dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0)) + return false; + } + } + + /* + * Having the range decoder always normalized when we are outside + * this function makes it easier to correctly handle end of the chunk. + */ + rc_normalize(&s->rc); + + return true; +} + +/* + * Reset the LZMA decoder and range decoder state. Dictionary is nore reset + * here, because LZMA state may be reset without resetting the dictionary. + */ +static void lzma_reset(struct xz_dec_lzma2 *s) +{ + uint16_t *probs; + size_t i; + + s->lzma.state = STATE_LIT_LIT; + s->lzma.rep0 = 0; + s->lzma.rep1 = 0; + s->lzma.rep2 = 0; + s->lzma.rep3 = 0; + + /* + * All probabilities are initialized to the same value. This hack + * makes the code smaller by avoiding a separate loop for each + * probability array. + * + * This could be optimized so that only that part of literal + * probabilities that are actually required. In the common case + * we would write 12 KiB less. + */ + probs = s->lzma.is_match[0]; + for (i = 0; i < PROBS_TOTAL; ++i) + probs[i] = RC_BIT_MODEL_TOTAL / 2; + + rc_reset(&s->rc); +} + +/* + * Decode and validate LZMA properties (lc/lp/pb) and calculate the bit masks + * from the decoded lp and pb values. On success, the LZMA decoder state is + * reset and true is returned. + */ +static bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props) +{ + if (props > (4 * 5 + 4) * 9 + 8) + return false; + + s->lzma.pos_mask = 0; + while (props >= 9 * 5) { + props -= 9 * 5; + ++s->lzma.pos_mask; + } + + s->lzma.pos_mask = (1 << s->lzma.pos_mask) - 1; + + s->lzma.literal_pos_mask = 0; + while (props >= 9) { + props -= 9; + ++s->lzma.literal_pos_mask; + } + + s->lzma.lc = props; + + if (s->lzma.lc + s->lzma.literal_pos_mask > 4) + return false; + + s->lzma.literal_pos_mask = (1 << s->lzma.literal_pos_mask) - 1; + + lzma_reset(s); + + return true; +} + +/********* + * LZMA2 * + *********/ + +/* + * The LZMA decoder assumes that if the input limit (s->rc.in_limit) hasn't + * been exceeded, it is safe to read up to LZMA_IN_REQUIRED bytes. This + * wrapper function takes care of making the LZMA decoder's assumption safe. + * + * As long as there is plenty of input left to be decoded in the current LZMA + * chunk, we decode directly from the caller-supplied input buffer until + * there's LZMA_IN_REQUIRED bytes left. Those remaining bytes are copied into + * s->temp.buf, which (hopefully) gets filled on the next call to this + * function. We decode a few bytes from the temporary buffer so that we can + * continue decoding from the caller-supplied input buffer again. + */ +static bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b) +{ + size_t in_avail; + uint32_t tmp; + + in_avail = b->in_size - b->in_pos; + if (s->temp.size > 0 || s->lzma2.compressed == 0) { + tmp = 2 * LZMA_IN_REQUIRED - s->temp.size; + if (tmp > s->lzma2.compressed - s->temp.size) + tmp = s->lzma2.compressed - s->temp.size; + if (tmp > in_avail) + tmp = in_avail; + + memcpy(s->temp.buf + s->temp.size, b->in + b->in_pos, tmp); + + if (s->temp.size + tmp == s->lzma2.compressed) { + memzero(s->temp.buf + s->temp.size + tmp, + sizeof(s->temp.buf) + - s->temp.size - tmp); + s->rc.in_limit = s->temp.size + tmp; + } else if (s->temp.size + tmp < LZMA_IN_REQUIRED) { + s->temp.size += tmp; + b->in_pos += tmp; + return true; + } else { + s->rc.in_limit = s->temp.size + tmp - LZMA_IN_REQUIRED; + } + + s->rc.in = s->temp.buf; + s->rc.in_pos = 0; + + if (!lzma_main(s) || s->rc.in_pos > s->temp.size + tmp) + return false; + + s->lzma2.compressed -= s->rc.in_pos; + + if (s->rc.in_pos < s->temp.size) { + s->temp.size -= s->rc.in_pos; + memmove(s->temp.buf, s->temp.buf + s->rc.in_pos, + s->temp.size); + return true; + } + + b->in_pos += s->rc.in_pos - s->temp.size; + s->temp.size = 0; + } + + in_avail = b->in_size - b->in_pos; + if (in_avail >= LZMA_IN_REQUIRED) { + s->rc.in = b->in; + s->rc.in_pos = b->in_pos; + + if (in_avail >= s->lzma2.compressed + LZMA_IN_REQUIRED) + s->rc.in_limit = b->in_pos + s->lzma2.compressed; + else + s->rc.in_limit = b->in_size - LZMA_IN_REQUIRED; + + if (!lzma_main(s)) + return false; + + in_avail = s->rc.in_pos - b->in_pos; + if (in_avail > s->lzma2.compressed) + return false; + + s->lzma2.compressed -= in_avail; + b->in_pos = s->rc.in_pos; + } + + in_avail = b->in_size - b->in_pos; + if (in_avail < LZMA_IN_REQUIRED) { + if (in_avail > s->lzma2.compressed) + in_avail = s->lzma2.compressed; + + memcpy(s->temp.buf, b->in + b->in_pos, in_avail); + s->temp.size = in_avail; + b->in_pos += in_avail; + } + + return true; +} + +/* + * Take care of the LZMA2 control layer, and forward the job of actual LZMA + * decoding or copying of uncompressed chunks to other functions. + */ +XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, + struct xz_buf *b) +{ + uint32_t tmp; + + while (b->in_pos < b->in_size || s->lzma2.sequence == SEQ_LZMA_RUN) { + switch (s->lzma2.sequence) { + case SEQ_CONTROL: + /* + * LZMA2 control byte + * + * Exact values: + * 0x00 End marker + * 0x01 Dictionary reset followed by + * an uncompressed chunk + * 0x02 Uncompressed chunk (no dictionary reset) + * + * Highest three bits (s->control & 0xE0): + * 0xE0 Dictionary reset, new properties and state + * reset, followed by LZMA compressed chunk + * 0xC0 New properties and state reset, followed + * by LZMA compressed chunk (no dictionary + * reset) + * 0xA0 State reset using old properties, + * followed by LZMA compressed chunk (no + * dictionary reset) + * 0x80 LZMA chunk (no dictionary or state reset) + * + * For LZMA compressed chunks, the lowest five bits + * (s->control & 1F) are the highest bits of the + * uncompressed size (bits 16-20). + * + * A new LZMA2 stream must begin with a dictionary + * reset. The first LZMA chunk must set new + * properties and reset the LZMA state. + * + * Values that don't match anything described above + * are invalid and we return XZ_DATA_ERROR. + */ + tmp = b->in[b->in_pos++]; + + if (tmp == 0x00) + return XZ_STREAM_END; + + if (tmp >= 0xE0 || tmp == 0x01) { + s->lzma2.need_props = true; + s->lzma2.need_dict_reset = false; + dict_reset(&s->dict, b); + } else if (s->lzma2.need_dict_reset) { + return XZ_DATA_ERROR; + } + + if (tmp >= 0x80) { + s->lzma2.uncompressed = (tmp & 0x1F) << 16; + s->lzma2.sequence = SEQ_UNCOMPRESSED_1; + + if (tmp >= 0xC0) { + /* + * When there are new properties, + * state reset is done at + * SEQ_PROPERTIES. + */ + s->lzma2.need_props = false; + s->lzma2.next_sequence + = SEQ_PROPERTIES; + + } else if (s->lzma2.need_props) { + return XZ_DATA_ERROR; + + } else { + s->lzma2.next_sequence + = SEQ_LZMA_PREPARE; + if (tmp >= 0xA0) + lzma_reset(s); + } + } else { + if (tmp > 0x02) + return XZ_DATA_ERROR; + + s->lzma2.sequence = SEQ_COMPRESSED_0; + s->lzma2.next_sequence = SEQ_COPY; + } + + break; + + case SEQ_UNCOMPRESSED_1: + s->lzma2.uncompressed + += (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.sequence = SEQ_UNCOMPRESSED_2; + break; + + case SEQ_UNCOMPRESSED_2: + s->lzma2.uncompressed + += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.sequence = SEQ_COMPRESSED_0; + break; + + case SEQ_COMPRESSED_0: + s->lzma2.compressed + = (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.sequence = SEQ_COMPRESSED_1; + break; + + case SEQ_COMPRESSED_1: + s->lzma2.compressed + += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.sequence = s->lzma2.next_sequence; + break; + + case SEQ_PROPERTIES: + if (!lzma_props(s, b->in[b->in_pos++])) + return XZ_DATA_ERROR; + + s->lzma2.sequence = SEQ_LZMA_PREPARE; + + case SEQ_LZMA_PREPARE: + if (s->lzma2.compressed < RC_INIT_BYTES) + return XZ_DATA_ERROR; + + if (!rc_read_init(&s->rc, b)) + return XZ_OK; + + s->lzma2.compressed -= RC_INIT_BYTES; + s->lzma2.sequence = SEQ_LZMA_RUN; + + case SEQ_LZMA_RUN: + /* + * Set dictionary limit to indicate how much we want + * to be encoded at maximum. Decode new data into the + * dictionary. Flush the new data from dictionary to + * b->out. Check if we finished decoding this chunk. + * In case the dictionary got full but we didn't fill + * the output buffer yet, we may run this loop + * multiple times without changing s->lzma2.sequence. + */ + dict_limit(&s->dict, min_t(size_t, + b->out_size - b->out_pos, + s->lzma2.uncompressed)); + if (!lzma2_lzma(s, b)) + return XZ_DATA_ERROR; + + s->lzma2.uncompressed -= dict_flush(&s->dict, b); + + if (s->lzma2.uncompressed == 0) { + if (s->lzma2.compressed > 0 || s->lzma.len > 0 + || !rc_is_finished(&s->rc)) + return XZ_DATA_ERROR; + + rc_reset(&s->rc); + s->lzma2.sequence = SEQ_CONTROL; + + } else if (b->out_pos == b->out_size + || (b->in_pos == b->in_size + && s->temp.size + < s->lzma2.compressed)) { + return XZ_OK; + } + + break; + + case SEQ_COPY: + dict_uncompressed(&s->dict, b, &s->lzma2.compressed); + if (s->lzma2.compressed > 0) + return XZ_OK; + + s->lzma2.sequence = SEQ_CONTROL; + break; + } + } + + return XZ_OK; +} + +XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, + uint32_t dict_max) +{ + struct xz_dec_lzma2 *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) + return NULL; + + s->dict.mode = mode; + s->dict.size_max = dict_max; + + if (DEC_IS_PREALLOC(mode)) { + s->dict.buf = vmalloc(dict_max); + if (s->dict.buf == NULL) { + kfree(s); + return NULL; + } + } else if (DEC_IS_DYNALLOC(mode)) { + s->dict.buf = NULL; + s->dict.allocated = 0; + } + + return s; +} + +XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, uint8_t props) +{ + /* This limits dictionary size to 3 GiB to keep parsing simpler. */ + if (props > 39) + return XZ_OPTIONS_ERROR; + + s->dict.size = 2 + (props & 1); + s->dict.size <<= (props >> 1) + 11; + + if (DEC_IS_MULTI(s->dict.mode)) { + if (s->dict.size > s->dict.size_max) + return XZ_MEMLIMIT_ERROR; + + s->dict.end = s->dict.size; + + if (DEC_IS_DYNALLOC(s->dict.mode)) { + if (s->dict.allocated < s->dict.size) { + vfree(s->dict.buf); + s->dict.buf = vmalloc(s->dict.size); + if (s->dict.buf == NULL) { + s->dict.allocated = 0; + return XZ_MEM_ERROR; + } + } + } + } + + s->lzma.len = 0; + + s->lzma2.sequence = SEQ_CONTROL; + s->lzma2.need_dict_reset = true; + + s->temp.size = 0; + + return XZ_OK; +} + +XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s) +{ + if (DEC_IS_MULTI(s->dict.mode)) + vfree(s->dict.buf); + + kfree(s); +} diff --git a/depends/xz-embedded/src/xz_dec_stream.c b/depends/xz-embedded/src/xz_dec_stream.c new file mode 100644 index 00000000..d6525506 --- /dev/null +++ b/depends/xz-embedded/src/xz_dec_stream.c @@ -0,0 +1,847 @@ +/* + * .xz Stream decoder + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#include "xz_private.h" +#include "xz_stream.h" + +#ifdef XZ_USE_CRC64 +# define IS_CRC64(check_type) ((check_type) == XZ_CHECK_CRC64) +#else +# define IS_CRC64(check_type) false +#endif + +/* Hash used to validate the Index field */ +struct xz_dec_hash { + vli_type unpadded; + vli_type uncompressed; + uint32_t crc32; +}; + +struct xz_dec { + /* Position in dec_main() */ + enum { + SEQ_STREAM_HEADER, + SEQ_BLOCK_START, + SEQ_BLOCK_HEADER, + SEQ_BLOCK_UNCOMPRESS, + SEQ_BLOCK_PADDING, + SEQ_BLOCK_CHECK, + SEQ_INDEX, + SEQ_INDEX_PADDING, + SEQ_INDEX_CRC32, + SEQ_STREAM_FOOTER + } sequence; + + /* Position in variable-length integers and Check fields */ + uint32_t pos; + + /* Variable-length integer decoded by dec_vli() */ + vli_type vli; + + /* Saved in_pos and out_pos */ + size_t in_start; + size_t out_start; + +#ifdef XZ_USE_CRC64 + /* CRC32 or CRC64 value in Block or CRC32 value in Index */ + uint64_t crc; +#else + /* CRC32 value in Block or Index */ + uint32_t crc; +#endif + + /* Type of the integrity check calculated from uncompressed data */ + enum xz_check check_type; + + /* Operation mode */ + enum xz_mode mode; + + /* + * True if the next call to xz_dec_run() is allowed to return + * XZ_BUF_ERROR. + */ + bool allow_buf_error; + + /* Information stored in Block Header */ + struct { + /* + * Value stored in the Compressed Size field, or + * VLI_UNKNOWN if Compressed Size is not present. + */ + vli_type compressed; + + /* + * Value stored in the Uncompressed Size field, or + * VLI_UNKNOWN if Uncompressed Size is not present. + */ + vli_type uncompressed; + + /* Size of the Block Header field */ + uint32_t size; + } block_header; + + /* Information collected when decoding Blocks */ + struct { + /* Observed compressed size of the current Block */ + vli_type compressed; + + /* Observed uncompressed size of the current Block */ + vli_type uncompressed; + + /* Number of Blocks decoded so far */ + vli_type count; + + /* + * Hash calculated from the Block sizes. This is used to + * validate the Index field. + */ + struct xz_dec_hash hash; + } block; + + /* Variables needed when verifying the Index field */ + struct { + /* Position in dec_index() */ + enum { + SEQ_INDEX_COUNT, + SEQ_INDEX_UNPADDED, + SEQ_INDEX_UNCOMPRESSED + } sequence; + + /* Size of the Index in bytes */ + vli_type size; + + /* Number of Records (matches block.count in valid files) */ + vli_type count; + + /* + * Hash calculated from the Records (matches block.hash in + * valid files). + */ + struct xz_dec_hash hash; + } index; + + /* + * Temporary buffer needed to hold Stream Header, Block Header, + * and Stream Footer. The Block Header is the biggest (1 KiB) + * so we reserve space according to that. buf[] has to be aligned + * to a multiple of four bytes; the size_t variables before it + * should guarantee this. + */ + struct { + size_t pos; + size_t size; + uint8_t buf[1024]; + } temp; + + struct xz_dec_lzma2 *lzma2; + +#ifdef XZ_DEC_BCJ + struct xz_dec_bcj *bcj; + bool bcj_active; +#endif +}; + +#ifdef XZ_DEC_ANY_CHECK +/* Sizes of the Check field with different Check IDs */ +static const uint8_t check_sizes[16] = { + 0, + 4, 4, 4, + 8, 8, 8, + 16, 16, 16, + 32, 32, 32, + 64, 64, 64 +}; +#endif + +/* + * Fill s->temp by copying data starting from b->in[b->in_pos]. Caller + * must have set s->temp.pos to indicate how much data we are supposed + * to copy into s->temp.buf. Return true once s->temp.pos has reached + * s->temp.size. + */ +static bool fill_temp(struct xz_dec *s, struct xz_buf *b) +{ + size_t copy_size = min_t(size_t, + b->in_size - b->in_pos, s->temp.size - s->temp.pos); + + memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size); + b->in_pos += copy_size; + s->temp.pos += copy_size; + + if (s->temp.pos == s->temp.size) { + s->temp.pos = 0; + return true; + } + + return false; +} + +/* Decode a variable-length integer (little-endian base-128 encoding) */ +static enum xz_ret dec_vli(struct xz_dec *s, const uint8_t *in, + size_t *in_pos, size_t in_size) +{ + uint8_t byte; + + if (s->pos == 0) + s->vli = 0; + + while (*in_pos < in_size) { + byte = in[*in_pos]; + ++*in_pos; + + s->vli |= (vli_type)(byte & 0x7F) << s->pos; + + if ((byte & 0x80) == 0) { + /* Don't allow non-minimal encodings. */ + if (byte == 0 && s->pos != 0) + return XZ_DATA_ERROR; + + s->pos = 0; + return XZ_STREAM_END; + } + + s->pos += 7; + if (s->pos == 7 * VLI_BYTES_MAX) + return XZ_DATA_ERROR; + } + + return XZ_OK; +} + +/* + * Decode the Compressed Data field from a Block. Update and validate + * the observed compressed and uncompressed sizes of the Block so that + * they don't exceed the values possibly stored in the Block Header + * (validation assumes that no integer overflow occurs, since vli_type + * is normally uint64_t). Update the CRC32 or CRC64 value if presence of + * the CRC32 or CRC64 field was indicated in Stream Header. + * + * Once the decoding is finished, validate that the observed sizes match + * the sizes possibly stored in the Block Header. Update the hash and + * Block count, which are later used to validate the Index field. + */ +static enum xz_ret dec_block(struct xz_dec *s, struct xz_buf *b) +{ + enum xz_ret ret; + + s->in_start = b->in_pos; + s->out_start = b->out_pos; + +#ifdef XZ_DEC_BCJ + if (s->bcj_active) + ret = xz_dec_bcj_run(s->bcj, s->lzma2, b); + else +#endif + ret = xz_dec_lzma2_run(s->lzma2, b); + + s->block.compressed += b->in_pos - s->in_start; + s->block.uncompressed += b->out_pos - s->out_start; + + /* + * There is no need to separately check for VLI_UNKNOWN, since + * the observed sizes are always smaller than VLI_UNKNOWN. + */ + if (s->block.compressed > s->block_header.compressed + || s->block.uncompressed + > s->block_header.uncompressed) + return XZ_DATA_ERROR; + + if (s->check_type == XZ_CHECK_CRC32) + s->crc = xz_crc32(b->out + s->out_start, + b->out_pos - s->out_start, s->crc); +#ifdef XZ_USE_CRC64 + else if (s->check_type == XZ_CHECK_CRC64) + s->crc = xz_crc64(b->out + s->out_start, + b->out_pos - s->out_start, s->crc); +#endif + + if (ret == XZ_STREAM_END) { + if (s->block_header.compressed != VLI_UNKNOWN + && s->block_header.compressed + != s->block.compressed) + return XZ_DATA_ERROR; + + if (s->block_header.uncompressed != VLI_UNKNOWN + && s->block_header.uncompressed + != s->block.uncompressed) + return XZ_DATA_ERROR; + + s->block.hash.unpadded += s->block_header.size + + s->block.compressed; + +#ifdef XZ_DEC_ANY_CHECK + s->block.hash.unpadded += check_sizes[s->check_type]; +#else + if (s->check_type == XZ_CHECK_CRC32) + s->block.hash.unpadded += 4; + else if (IS_CRC64(s->check_type)) + s->block.hash.unpadded += 8; +#endif + + s->block.hash.uncompressed += s->block.uncompressed; + s->block.hash.crc32 = xz_crc32( + (const uint8_t *)&s->block.hash, + sizeof(s->block.hash), s->block.hash.crc32); + + ++s->block.count; + } + + return ret; +} + +/* Update the Index size and the CRC32 value. */ +static void index_update(struct xz_dec *s, const struct xz_buf *b) +{ + size_t in_used = b->in_pos - s->in_start; + s->index.size += in_used; + s->crc = xz_crc32(b->in + s->in_start, in_used, s->crc); +} + +/* + * Decode the Number of Records, Unpadded Size, and Uncompressed Size + * fields from the Index field. That is, Index Padding and CRC32 are not + * decoded by this function. + * + * This can return XZ_OK (more input needed), XZ_STREAM_END (everything + * successfully decoded), or XZ_DATA_ERROR (input is corrupt). + */ +static enum xz_ret dec_index(struct xz_dec *s, struct xz_buf *b) +{ + enum xz_ret ret; + + do { + ret = dec_vli(s, b->in, &b->in_pos, b->in_size); + if (ret != XZ_STREAM_END) { + index_update(s, b); + return ret; + } + + switch (s->index.sequence) { + case SEQ_INDEX_COUNT: + s->index.count = s->vli; + + /* + * Validate that the Number of Records field + * indicates the same number of Records as + * there were Blocks in the Stream. + */ + if (s->index.count != s->block.count) + return XZ_DATA_ERROR; + + s->index.sequence = SEQ_INDEX_UNPADDED; + break; + + case SEQ_INDEX_UNPADDED: + s->index.hash.unpadded += s->vli; + s->index.sequence = SEQ_INDEX_UNCOMPRESSED; + break; + + case SEQ_INDEX_UNCOMPRESSED: + s->index.hash.uncompressed += s->vli; + s->index.hash.crc32 = xz_crc32( + (const uint8_t *)&s->index.hash, + sizeof(s->index.hash), + s->index.hash.crc32); + --s->index.count; + s->index.sequence = SEQ_INDEX_UNPADDED; + break; + } + } while (s->index.count > 0); + + return XZ_STREAM_END; +} + +/* + * Validate that the next four or eight input bytes match the value + * of s->crc. s->pos must be zero when starting to validate the first byte. + * The "bits" argument allows using the same code for both CRC32 and CRC64. + */ +static enum xz_ret crc_validate(struct xz_dec *s, struct xz_buf *b, + uint32_t bits) +{ + do { + if (b->in_pos == b->in_size) + return XZ_OK; + + if (((s->crc >> s->pos) & 0xFF) != b->in[b->in_pos++]) + return XZ_DATA_ERROR; + + s->pos += 8; + + } while (s->pos < bits); + + s->crc = 0; + s->pos = 0; + + return XZ_STREAM_END; +} + +#ifdef XZ_DEC_ANY_CHECK +/* + * Skip over the Check field when the Check ID is not supported. + * Returns true once the whole Check field has been skipped over. + */ +static bool check_skip(struct xz_dec *s, struct xz_buf *b) +{ + while (s->pos < check_sizes[s->check_type]) { + if (b->in_pos == b->in_size) + return false; + + ++b->in_pos; + ++s->pos; + } + + s->pos = 0; + + return true; +} +#endif + +/* Decode the Stream Header field (the first 12 bytes of the .xz Stream). */ +static enum xz_ret dec_stream_header(struct xz_dec *s) +{ + if (!memeq(s->temp.buf, HEADER_MAGIC, HEADER_MAGIC_SIZE)) + return XZ_FORMAT_ERROR; + + if (xz_crc32(s->temp.buf + HEADER_MAGIC_SIZE, 2, 0) + != get_le32(s->temp.buf + HEADER_MAGIC_SIZE + 2)) + return XZ_DATA_ERROR; + + if (s->temp.buf[HEADER_MAGIC_SIZE] != 0) + return XZ_OPTIONS_ERROR; + + /* + * Of integrity checks, we support none (Check ID = 0), + * CRC32 (Check ID = 1), and optionally CRC64 (Check ID = 4). + * However, if XZ_DEC_ANY_CHECK is defined, we will accept other + * check types too, but then the check won't be verified and + * a warning (XZ_UNSUPPORTED_CHECK) will be given. + */ + s->check_type = s->temp.buf[HEADER_MAGIC_SIZE + 1]; + +#ifdef XZ_DEC_ANY_CHECK + if (s->check_type > XZ_CHECK_MAX) + return XZ_OPTIONS_ERROR; + + if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) + return XZ_UNSUPPORTED_CHECK; +#else + if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) + return XZ_OPTIONS_ERROR; +#endif + + return XZ_OK; +} + +/* Decode the Stream Footer field (the last 12 bytes of the .xz Stream) */ +static enum xz_ret dec_stream_footer(struct xz_dec *s) +{ + if (!memeq(s->temp.buf + 10, FOOTER_MAGIC, FOOTER_MAGIC_SIZE)) + return XZ_DATA_ERROR; + + if (xz_crc32(s->temp.buf + 4, 6, 0) != get_le32(s->temp.buf)) + return XZ_DATA_ERROR; + + /* + * Validate Backward Size. Note that we never added the size of the + * Index CRC32 field to s->index.size, thus we use s->index.size / 4 + * instead of s->index.size / 4 - 1. + */ + if ((s->index.size >> 2) != get_le32(s->temp.buf + 4)) + return XZ_DATA_ERROR; + + if (s->temp.buf[8] != 0 || s->temp.buf[9] != s->check_type) + return XZ_DATA_ERROR; + + /* + * Use XZ_STREAM_END instead of XZ_OK to be more convenient + * for the caller. + */ + return XZ_STREAM_END; +} + +/* Decode the Block Header and initialize the filter chain. */ +static enum xz_ret dec_block_header(struct xz_dec *s) +{ + enum xz_ret ret; + + /* + * Validate the CRC32. We know that the temp buffer is at least + * eight bytes so this is safe. + */ + s->temp.size -= 4; + if (xz_crc32(s->temp.buf, s->temp.size, 0) + != get_le32(s->temp.buf + s->temp.size)) + return XZ_DATA_ERROR; + + s->temp.pos = 2; + + /* + * Catch unsupported Block Flags. We support only one or two filters + * in the chain, so we catch that with the same test. + */ +#ifdef XZ_DEC_BCJ + if (s->temp.buf[1] & 0x3E) +#else + if (s->temp.buf[1] & 0x3F) +#endif + return XZ_OPTIONS_ERROR; + + /* Compressed Size */ + if (s->temp.buf[1] & 0x40) { + if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) + != XZ_STREAM_END) + return XZ_DATA_ERROR; + + s->block_header.compressed = s->vli; + } else { + s->block_header.compressed = VLI_UNKNOWN; + } + + /* Uncompressed Size */ + if (s->temp.buf[1] & 0x80) { + if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) + != XZ_STREAM_END) + return XZ_DATA_ERROR; + + s->block_header.uncompressed = s->vli; + } else { + s->block_header.uncompressed = VLI_UNKNOWN; + } + +#ifdef XZ_DEC_BCJ + /* If there are two filters, the first one must be a BCJ filter. */ + s->bcj_active = s->temp.buf[1] & 0x01; + if (s->bcj_active) { + if (s->temp.size - s->temp.pos < 2) + return XZ_OPTIONS_ERROR; + + ret = xz_dec_bcj_reset(s->bcj, s->temp.buf[s->temp.pos++]); + if (ret != XZ_OK) + return ret; + + /* + * We don't support custom start offset, + * so Size of Properties must be zero. + */ + if (s->temp.buf[s->temp.pos++] != 0x00) + return XZ_OPTIONS_ERROR; + } +#endif + + /* Valid Filter Flags always take at least two bytes. */ + if (s->temp.size - s->temp.pos < 2) + return XZ_DATA_ERROR; + + /* Filter ID = LZMA2 */ + if (s->temp.buf[s->temp.pos++] != 0x21) + return XZ_OPTIONS_ERROR; + + /* Size of Properties = 1-byte Filter Properties */ + if (s->temp.buf[s->temp.pos++] != 0x01) + return XZ_OPTIONS_ERROR; + + /* Filter Properties contains LZMA2 dictionary size. */ + if (s->temp.size - s->temp.pos < 1) + return XZ_DATA_ERROR; + + ret = xz_dec_lzma2_reset(s->lzma2, s->temp.buf[s->temp.pos++]); + if (ret != XZ_OK) + return ret; + + /* The rest must be Header Padding. */ + while (s->temp.pos < s->temp.size) + if (s->temp.buf[s->temp.pos++] != 0x00) + return XZ_OPTIONS_ERROR; + + s->temp.pos = 0; + s->block.compressed = 0; + s->block.uncompressed = 0; + + return XZ_OK; +} + +static enum xz_ret dec_main(struct xz_dec *s, struct xz_buf *b) +{ + enum xz_ret ret; + + /* + * Store the start position for the case when we are in the middle + * of the Index field. + */ + s->in_start = b->in_pos; + + while (true) { + switch (s->sequence) { + case SEQ_STREAM_HEADER: + /* + * Stream Header is copied to s->temp, and then + * decoded from there. This way if the caller + * gives us only little input at a time, we can + * still keep the Stream Header decoding code + * simple. Similar approach is used in many places + * in this file. + */ + if (!fill_temp(s, b)) + return XZ_OK; + + /* + * If dec_stream_header() returns + * XZ_UNSUPPORTED_CHECK, it is still possible + * to continue decoding if working in multi-call + * mode. Thus, update s->sequence before calling + * dec_stream_header(). + */ + s->sequence = SEQ_BLOCK_START; + + ret = dec_stream_header(s); + if (ret != XZ_OK) + return ret; + + case SEQ_BLOCK_START: + /* We need one byte of input to continue. */ + if (b->in_pos == b->in_size) + return XZ_OK; + + /* See if this is the beginning of the Index field. */ + if (b->in[b->in_pos] == 0) { + s->in_start = b->in_pos++; + s->sequence = SEQ_INDEX; + break; + } + + /* + * Calculate the size of the Block Header and + * prepare to decode it. + */ + s->block_header.size + = ((uint32_t)b->in[b->in_pos] + 1) * 4; + + s->temp.size = s->block_header.size; + s->temp.pos = 0; + s->sequence = SEQ_BLOCK_HEADER; + + case SEQ_BLOCK_HEADER: + if (!fill_temp(s, b)) + return XZ_OK; + + ret = dec_block_header(s); + if (ret != XZ_OK) + return ret; + + s->sequence = SEQ_BLOCK_UNCOMPRESS; + + case SEQ_BLOCK_UNCOMPRESS: + ret = dec_block(s, b); + if (ret != XZ_STREAM_END) + return ret; + + s->sequence = SEQ_BLOCK_PADDING; + + case SEQ_BLOCK_PADDING: + /* + * Size of Compressed Data + Block Padding + * must be a multiple of four. We don't need + * s->block.compressed for anything else + * anymore, so we use it here to test the size + * of the Block Padding field. + */ + while (s->block.compressed & 3) { + if (b->in_pos == b->in_size) + return XZ_OK; + + if (b->in[b->in_pos++] != 0) + return XZ_DATA_ERROR; + + ++s->block.compressed; + } + + s->sequence = SEQ_BLOCK_CHECK; + + case SEQ_BLOCK_CHECK: + if (s->check_type == XZ_CHECK_CRC32) { + ret = crc_validate(s, b, 32); + if (ret != XZ_STREAM_END) + return ret; + } + else if (IS_CRC64(s->check_type)) { + ret = crc_validate(s, b, 64); + if (ret != XZ_STREAM_END) + return ret; + } +#ifdef XZ_DEC_ANY_CHECK + else if (!check_skip(s, b)) { + return XZ_OK; + } +#endif + + s->sequence = SEQ_BLOCK_START; + break; + + case SEQ_INDEX: + ret = dec_index(s, b); + if (ret != XZ_STREAM_END) + return ret; + + s->sequence = SEQ_INDEX_PADDING; + + case SEQ_INDEX_PADDING: + while ((s->index.size + (b->in_pos - s->in_start)) + & 3) { + if (b->in_pos == b->in_size) { + index_update(s, b); + return XZ_OK; + } + + if (b->in[b->in_pos++] != 0) + return XZ_DATA_ERROR; + } + + /* Finish the CRC32 value and Index size. */ + index_update(s, b); + + /* Compare the hashes to validate the Index field. */ + if (!memeq(&s->block.hash, &s->index.hash, + sizeof(s->block.hash))) + return XZ_DATA_ERROR; + + s->sequence = SEQ_INDEX_CRC32; + + case SEQ_INDEX_CRC32: + ret = crc_validate(s, b, 32); + if (ret != XZ_STREAM_END) + return ret; + + s->temp.size = STREAM_HEADER_SIZE; + s->sequence = SEQ_STREAM_FOOTER; + + case SEQ_STREAM_FOOTER: + if (!fill_temp(s, b)) + return XZ_OK; + + return dec_stream_footer(s); + } + } + + /* Never reached */ +} + +/* + * xz_dec_run() is a wrapper for dec_main() to handle some special cases in + * multi-call and single-call decoding. + * + * In multi-call mode, we must return XZ_BUF_ERROR when it seems clear that we + * are not going to make any progress anymore. This is to prevent the caller + * from calling us infinitely when the input file is truncated or otherwise + * corrupt. Since zlib-style API allows that the caller fills the input buffer + * only when the decoder doesn't produce any new output, we have to be careful + * to avoid returning XZ_BUF_ERROR too easily: XZ_BUF_ERROR is returned only + * after the second consecutive call to xz_dec_run() that makes no progress. + * + * In single-call mode, if we couldn't decode everything and no error + * occurred, either the input is truncated or the output buffer is too small. + * Since we know that the last input byte never produces any output, we know + * that if all the input was consumed and decoding wasn't finished, the file + * must be corrupt. Otherwise the output buffer has to be too small or the + * file is corrupt in a way that decoding it produces too big output. + * + * If single-call decoding fails, we reset b->in_pos and b->out_pos back to + * their original values. This is because with some filter chains there won't + * be any valid uncompressed data in the output buffer unless the decoding + * actually succeeds (that's the price to pay of using the output buffer as + * the workspace). + */ +XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b) +{ + size_t in_start; + size_t out_start; + enum xz_ret ret; + + if (DEC_IS_SINGLE(s->mode)) + xz_dec_reset(s); + + in_start = b->in_pos; + out_start = b->out_pos; + ret = dec_main(s, b); + + if (DEC_IS_SINGLE(s->mode)) { + if (ret == XZ_OK) + ret = b->in_pos == b->in_size + ? XZ_DATA_ERROR : XZ_BUF_ERROR; + + if (ret != XZ_STREAM_END) { + b->in_pos = in_start; + b->out_pos = out_start; + } + + } else if (ret == XZ_OK && in_start == b->in_pos + && out_start == b->out_pos) { + if (s->allow_buf_error) + ret = XZ_BUF_ERROR; + + s->allow_buf_error = true; + } else { + s->allow_buf_error = false; + } + + return ret; +} + +XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max) +{ + struct xz_dec *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) + return NULL; + + s->mode = mode; + +#ifdef XZ_DEC_BCJ + s->bcj = xz_dec_bcj_create(DEC_IS_SINGLE(mode)); + if (s->bcj == NULL) + goto error_bcj; +#endif + + s->lzma2 = xz_dec_lzma2_create(mode, dict_max); + if (s->lzma2 == NULL) + goto error_lzma2; + + xz_dec_reset(s); + return s; + +error_lzma2: +#ifdef XZ_DEC_BCJ + xz_dec_bcj_end(s->bcj); +error_bcj: +#endif + kfree(s); + return NULL; +} + +XZ_EXTERN void xz_dec_reset(struct xz_dec *s) +{ + s->sequence = SEQ_STREAM_HEADER; + s->allow_buf_error = false; + s->pos = 0; + s->crc = 0; + memzero(&s->block, sizeof(s->block)); + memzero(&s->index, sizeof(s->index)); + s->temp.pos = 0; + s->temp.size = STREAM_HEADER_SIZE; +} + +XZ_EXTERN void xz_dec_end(struct xz_dec *s) +{ + if (s != NULL) { + xz_dec_lzma2_end(s->lzma2); +#ifdef XZ_DEC_BCJ + xz_dec_bcj_end(s->bcj); +#endif + kfree(s); + } +} diff --git a/depends/xz-embedded/src/xz_lzma2.h b/depends/xz-embedded/src/xz_lzma2.h new file mode 100644 index 00000000..071d67be --- /dev/null +++ b/depends/xz-embedded/src/xz_lzma2.h @@ -0,0 +1,204 @@ +/* + * LZMA2 definitions + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <http://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_LZMA2_H +#define XZ_LZMA2_H + +/* Range coder constants */ +#define RC_SHIFT_BITS 8 +#define RC_TOP_BITS 24 +#define RC_TOP_VALUE (1 << RC_TOP_BITS) +#define RC_BIT_MODEL_TOTAL_BITS 11 +#define RC_BIT_MODEL_TOTAL (1 << RC_BIT_MODEL_TOTAL_BITS) +#define RC_MOVE_BITS 5 + +/* + * Maximum number of position states. A position state is the lowest pb + * number of bits of the current uncompressed offset. In some places there + * are different sets of probabilities for different position states. + */ +#define POS_STATES_MAX (1 << 4) + +/* + * This enum is used to track which LZMA symbols have occurred most recently + * and in which order. This information is used to predict the next symbol. + * + * Symbols: + * - Literal: One 8-bit byte + * - Match: Repeat a chunk of data at some distance + * - Long repeat: Multi-byte match at a recently seen distance + * - Short repeat: One-byte repeat at a recently seen distance + * + * The symbol names are in from STATE_oldest_older_previous. REP means + * either short or long repeated match, and NONLIT means any non-literal. + */ +enum lzma_state { + STATE_LIT_LIT, + STATE_MATCH_LIT_LIT, + STATE_REP_LIT_LIT, + STATE_SHORTREP_LIT_LIT, + STATE_MATCH_LIT, + STATE_REP_LIT, + STATE_SHORTREP_LIT, + STATE_LIT_MATCH, + STATE_LIT_LONGREP, + STATE_LIT_SHORTREP, + STATE_NONLIT_MATCH, + STATE_NONLIT_REP +}; + +/* Total number of states */ +#define STATES 12 + +/* The lowest 7 states indicate that the previous state was a literal. */ +#define LIT_STATES 7 + +/* Indicate that the latest symbol was a literal. */ +static inline void lzma_state_literal(enum lzma_state *state) +{ + if (*state <= STATE_SHORTREP_LIT_LIT) + *state = STATE_LIT_LIT; + else if (*state <= STATE_LIT_SHORTREP) + *state -= 3; + else + *state -= 6; +} + +/* Indicate that the latest symbol was a match. */ +static inline void lzma_state_match(enum lzma_state *state) +{ + *state = *state < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH; +} + +/* Indicate that the latest state was a long repeated match. */ +static inline void lzma_state_long_rep(enum lzma_state *state) +{ + *state = *state < LIT_STATES ? STATE_LIT_LONGREP : STATE_NONLIT_REP; +} + +/* Indicate that the latest symbol was a short match. */ +static inline void lzma_state_short_rep(enum lzma_state *state) +{ + *state = *state < LIT_STATES ? STATE_LIT_SHORTREP : STATE_NONLIT_REP; +} + +/* Test if the previous symbol was a literal. */ +static inline bool lzma_state_is_literal(enum lzma_state state) +{ + return state < LIT_STATES; +} + +/* Each literal coder is divided in three sections: + * - 0x001-0x0FF: Without match byte + * - 0x101-0x1FF: With match byte; match bit is 0 + * - 0x201-0x2FF: With match byte; match bit is 1 + * + * Match byte is used when the previous LZMA symbol was something else than + * a literal (that is, it was some kind of match). + */ +#define LITERAL_CODER_SIZE 0x300 + +/* Maximum number of literal coders */ +#define LITERAL_CODERS_MAX (1 << 4) + +/* Minimum length of a match is two bytes. */ +#define MATCH_LEN_MIN 2 + +/* Match length is encoded with 4, 5, or 10 bits. + * + * Length Bits + * 2-9 4 = Choice=0 + 3 bits + * 10-17 5 = Choice=1 + Choice2=0 + 3 bits + * 18-273 10 = Choice=1 + Choice2=1 + 8 bits + */ +#define LEN_LOW_BITS 3 +#define LEN_LOW_SYMBOLS (1 << LEN_LOW_BITS) +#define LEN_MID_BITS 3 +#define LEN_MID_SYMBOLS (1 << LEN_MID_BITS) +#define LEN_HIGH_BITS 8 +#define LEN_HIGH_SYMBOLS (1 << LEN_HIGH_BITS) +#define LEN_SYMBOLS (LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS + LEN_HIGH_SYMBOLS) + +/* + * Maximum length of a match is 273 which is a result of the encoding + * described above. + */ +#define MATCH_LEN_MAX (MATCH_LEN_MIN + LEN_SYMBOLS - 1) + +/* + * Different sets of probabilities are used for match distances that have + * very short match length: Lengths of 2, 3, and 4 bytes have a separate + * set of probabilities for each length. The matches with longer length + * use a shared set of probabilities. + */ +#define DIST_STATES 4 + +/* + * Get the index of the appropriate probability array for decoding + * the distance slot. + */ +static inline uint32_t lzma_get_dist_state(uint32_t len) +{ + return len < DIST_STATES + MATCH_LEN_MIN + ? len - MATCH_LEN_MIN : DIST_STATES - 1; +} + +/* + * The highest two bits of a 32-bit match distance are encoded using six bits. + * This six-bit value is called a distance slot. This way encoding a 32-bit + * value takes 6-36 bits, larger values taking more bits. + */ +#define DIST_SLOT_BITS 6 +#define DIST_SLOTS (1 << DIST_SLOT_BITS) + +/* Match distances up to 127 are fully encoded using probabilities. Since + * the highest two bits (distance slot) are always encoded using six bits, + * the distances 0-3 don't need any additional bits to encode, since the + * distance slot itself is the same as the actual distance. DIST_MODEL_START + * indicates the first distance slot where at least one additional bit is + * needed. + */ +#define DIST_MODEL_START 4 + +/* + * Match distances greater than 127 are encoded in three pieces: + * - distance slot: the highest two bits + * - direct bits: 2-26 bits below the highest two bits + * - alignment bits: four lowest bits + * + * Direct bits don't use any probabilities. + * + * The distance slot value of 14 is for distances 128-191. + */ +#define DIST_MODEL_END 14 + +/* Distance slots that indicate a distance <= 127. */ +#define FULL_DISTANCES_BITS (DIST_MODEL_END / 2) +#define FULL_DISTANCES (1 << FULL_DISTANCES_BITS) + +/* + * For match distances greater than 127, only the highest two bits and the + * lowest four bits (alignment) is encoded using probabilities. + */ +#define ALIGN_BITS 4 +#define ALIGN_SIZE (1 << ALIGN_BITS) +#define ALIGN_MASK (ALIGN_SIZE - 1) + +/* Total number of all probability variables */ +#define PROBS_TOTAL (1846 + LITERAL_CODERS_MAX * LITERAL_CODER_SIZE) + +/* + * LZMA remembers the four most recent match distances. Reusing these + * distances tends to take less space than re-encoding the actual + * distance value. + */ +#define REPS 4 + +#endif diff --git a/depends/xz-embedded/src/xz_private.h b/depends/xz-embedded/src/xz_private.h new file mode 100644 index 00000000..482b90f3 --- /dev/null +++ b/depends/xz-embedded/src/xz_private.h @@ -0,0 +1,156 @@ +/* + * Private includes and definitions + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_PRIVATE_H +#define XZ_PRIVATE_H + +#ifdef __KERNEL__ +# include <linux/xz.h> +# include <linux/kernel.h> +# include <asm/unaligned.h> + /* XZ_PREBOOT may be defined only via decompress_unxz.c. */ +# ifndef XZ_PREBOOT +# include <linux/slab.h> +# include <linux/vmalloc.h> +# include <linux/string.h> +# ifdef CONFIG_XZ_DEC_X86 +# define XZ_DEC_X86 +# endif +# ifdef CONFIG_XZ_DEC_POWERPC +# define XZ_DEC_POWERPC +# endif +# ifdef CONFIG_XZ_DEC_IA64 +# define XZ_DEC_IA64 +# endif +# ifdef CONFIG_XZ_DEC_ARM +# define XZ_DEC_ARM +# endif +# ifdef CONFIG_XZ_DEC_ARMTHUMB +# define XZ_DEC_ARMTHUMB +# endif +# ifdef CONFIG_XZ_DEC_SPARC +# define XZ_DEC_SPARC +# endif +# define memeq(a, b, size) (memcmp(a, b, size) == 0) +# define memzero(buf, size) memset(buf, 0, size) +# endif +# define get_le32(p) le32_to_cpup((const uint32_t *)(p)) +#else + /* + * For userspace builds, use a separate header to define the required + * macros and functions. This makes it easier to adapt the code into + * different environments and avoids clutter in the Linux kernel tree. + */ +# include "xz_config.h" +#endif + +/* If no specific decoding mode is requested, enable support for all modes. */ +#if !defined(XZ_DEC_SINGLE) && !defined(XZ_DEC_PREALLOC) \ + && !defined(XZ_DEC_DYNALLOC) +# define XZ_DEC_SINGLE +# define XZ_DEC_PREALLOC +# define XZ_DEC_DYNALLOC +#endif + +/* + * The DEC_IS_foo(mode) macros are used in "if" statements. If only some + * of the supported modes are enabled, these macros will evaluate to true or + * false at compile time and thus allow the compiler to omit unneeded code. + */ +#ifdef XZ_DEC_SINGLE +# define DEC_IS_SINGLE(mode) ((mode) == XZ_SINGLE) +#else +# define DEC_IS_SINGLE(mode) (false) +#endif + +#ifdef XZ_DEC_PREALLOC +# define DEC_IS_PREALLOC(mode) ((mode) == XZ_PREALLOC) +#else +# define DEC_IS_PREALLOC(mode) (false) +#endif + +#ifdef XZ_DEC_DYNALLOC +# define DEC_IS_DYNALLOC(mode) ((mode) == XZ_DYNALLOC) +#else +# define DEC_IS_DYNALLOC(mode) (false) +#endif + +#if !defined(XZ_DEC_SINGLE) +# define DEC_IS_MULTI(mode) (true) +#elif defined(XZ_DEC_PREALLOC) || defined(XZ_DEC_DYNALLOC) +# define DEC_IS_MULTI(mode) ((mode) != XZ_SINGLE) +#else +# define DEC_IS_MULTI(mode) (false) +#endif + +/* + * If any of the BCJ filter decoders are wanted, define XZ_DEC_BCJ. + * XZ_DEC_BCJ is used to enable generic support for BCJ decoders. + */ +#ifndef XZ_DEC_BCJ +# if defined(XZ_DEC_X86) || defined(XZ_DEC_POWERPC) \ + || defined(XZ_DEC_IA64) || defined(XZ_DEC_ARM) \ + || defined(XZ_DEC_ARM) || defined(XZ_DEC_ARMTHUMB) \ + || defined(XZ_DEC_SPARC) +# define XZ_DEC_BCJ +# endif +#endif + +/* + * Allocate memory for LZMA2 decoder. xz_dec_lzma2_reset() must be used + * before calling xz_dec_lzma2_run(). + */ +XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, + uint32_t dict_max); + +/* + * Decode the LZMA2 properties (one byte) and reset the decoder. Return + * XZ_OK on success, XZ_MEMLIMIT_ERROR if the preallocated dictionary is not + * big enough, and XZ_OPTIONS_ERROR if props indicates something that this + * decoder doesn't support. + */ +XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, + uint8_t props); + +/* Decode raw LZMA2 stream from b->in to b->out. */ +XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, + struct xz_buf *b); + +/* Free the memory allocated for the LZMA2 decoder. */ +XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s); + +#ifdef XZ_DEC_BCJ +/* + * Allocate memory for BCJ decoders. xz_dec_bcj_reset() must be used before + * calling xz_dec_bcj_run(). + */ +XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call); + +/* + * Decode the Filter ID of a BCJ filter. This implementation doesn't + * support custom start offsets, so no decoding of Filter Properties + * is needed. Returns XZ_OK if the given Filter ID is supported. + * Otherwise XZ_OPTIONS_ERROR is returned. + */ +XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id); + +/* + * Decode raw BCJ + LZMA2 stream. This must be used only if there actually is + * a BCJ filter in the chain. If the chain has only LZMA2, xz_dec_lzma2_run() + * must be called directly. + */ +XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, + struct xz_dec_lzma2 *lzma2, + struct xz_buf *b); + +/* Free the memory allocated for the BCJ filters. */ +#define xz_dec_bcj_end(s) kfree(s) +#endif + +#endif diff --git a/depends/xz-embedded/src/xz_stream.h b/depends/xz-embedded/src/xz_stream.h new file mode 100644 index 00000000..66cb5a70 --- /dev/null +++ b/depends/xz-embedded/src/xz_stream.h @@ -0,0 +1,62 @@ +/* + * Definitions for handling the .xz file format + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +#ifndef XZ_STREAM_H +#define XZ_STREAM_H + +#if defined(__KERNEL__) && !XZ_INTERNAL_CRC32 +# include <linux/crc32.h> +# undef crc32 +# define xz_crc32(buf, size, crc) \ + (~crc32_le(~(uint32_t)(crc), buf, size)) +#endif + +/* + * See the .xz file format specification at + * http://tukaani.org/xz/xz-file-format.txt + * to understand the container format. + */ + +#define STREAM_HEADER_SIZE 12 + +#define HEADER_MAGIC "\3757zXZ" +#define HEADER_MAGIC_SIZE 6 + +#define FOOTER_MAGIC "YZ" +#define FOOTER_MAGIC_SIZE 2 + +/* + * Variable-length integer can hold a 63-bit unsigned integer or a special + * value indicating that the value is unknown. + * + * Experimental: vli_type can be defined to uint32_t to save a few bytes + * in code size (no effect on speed). Doing so limits the uncompressed and + * compressed size of the file to less than 256 MiB and may also weaken + * error detection slightly. + */ +typedef uint64_t vli_type; + +#define VLI_MAX ((vli_type)-1 / 2) +#define VLI_UNKNOWN ((vli_type)-1) + +/* Maximum encoded size of a VLI */ +#define VLI_BYTES_MAX (sizeof(vli_type) * 8 / 7) + +/* Integrity Check types */ +enum xz_check { + XZ_CHECK_NONE = 0, + XZ_CHECK_CRC32 = 1, + XZ_CHECK_CRC64 = 4, + XZ_CHECK_SHA256 = 10 +}; + +/* Maximum possible Check ID */ +#define XZ_CHECK_MAX 15 + +#endif diff --git a/depends/xz-embedded/xzminidec.c b/depends/xz-embedded/xzminidec.c new file mode 100644 index 00000000..ba074131 --- /dev/null +++ b/depends/xz-embedded/xzminidec.c @@ -0,0 +1,135 @@ +/* + * Simple XZ decoder command line tool + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +/* + * This is really limited: Not all filters from .xz format are supported, + * only CRC32 is supported as the integrity check, and decoding of + * concatenated .xz streams is not supported. Thus, you may want to look + * at xzdec from XZ Utils if a few KiB bigger tool is not a problem. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include "xz.h" + +static uint8_t in[BUFSIZ]; +static uint8_t out[BUFSIZ]; + +int main(int argc, char **argv) +{ + struct xz_buf b; + struct xz_dec *s; + enum xz_ret ret; + const char *msg; + + if (argc >= 2 && strcmp(argv[1], "--help") == 0) { + fputs("Uncompress a .xz file from stdin to stdout.\n" + "Arguments other than `--help' are ignored.\n", + stdout); + return 0; + } + + xz_crc32_init(); +#ifdef XZ_USE_CRC64 + xz_crc64_init(); +#endif + + /* + * Support up to 64 MiB dictionary. The actually needed memory + * is allocated once the headers have been parsed. + */ + s = xz_dec_init(XZ_DYNALLOC, 1 << 26); + if (s == NULL) { + msg = "Memory allocation failed\n"; + goto error; + } + + b.in = in; + b.in_pos = 0; + b.in_size = 0; + b.out = out; + b.out_pos = 0; + b.out_size = BUFSIZ; + + while (true) { + if (b.in_pos == b.in_size) { + b.in_size = fread(in, 1, sizeof(in), stdin); + b.in_pos = 0; + } + + ret = xz_dec_run(s, &b); + + if (b.out_pos == sizeof(out)) { + if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos) { + msg = "Write error\n"; + goto error; + } + + b.out_pos = 0; + } + + if (ret == XZ_OK) + continue; + +#ifdef XZ_DEC_ANY_CHECK + if (ret == XZ_UNSUPPORTED_CHECK) { + fputs(argv[0], stderr); + fputs(": ", stderr); + fputs("Unsupported check; not verifying " + "file integrity\n", stderr); + continue; + } +#endif + + if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos + || fclose(stdout)) { + msg = "Write error\n"; + goto error; + } + + switch (ret) { + case XZ_STREAM_END: + xz_dec_end(s); + return 0; + + case XZ_MEM_ERROR: + msg = "Memory allocation failed\n"; + goto error; + + case XZ_MEMLIMIT_ERROR: + msg = "Memory usage limit reached\n"; + goto error; + + case XZ_FORMAT_ERROR: + msg = "Not a .xz file\n"; + goto error; + + case XZ_OPTIONS_ERROR: + msg = "Unsupported options in the .xz headers\n"; + goto error; + + case XZ_DATA_ERROR: + case XZ_BUF_ERROR: + msg = "File is corrupt\n"; + goto error; + + default: + msg = "Bug!\n"; + goto error; + } + } + +error: + xz_dec_end(s); + fputs(argv[0], stderr); + fputs(": ", stderr); + fputs(msg, stderr); + return 1; +} diff --git a/gui/EditNotesDialog.cpp b/gui/EditNotesDialog.cpp index 6cc389f6..535ca804 100644 --- a/gui/EditNotesDialog.cpp +++ b/gui/EditNotesDialog.cpp @@ -1,5 +1,6 @@ #include "EditNotesDialog.h" #include "ui_EditNotesDialog.h" +#include "gui/platform.h" #include <QIcon> #include <QApplication> @@ -10,9 +11,10 @@ EditNotesDialog::EditNotesDialog( QString notes, QString name, QWidget* parent ) QDialog(parent), ui(new Ui::EditNotesDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); ui->noteEditor->setText(notes); - setWindowTitle("Edit notes of " + m_instance_name); + setWindowTitle(tr("Edit notes of %1").arg(m_instance_name)); //connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); } diff --git a/gui/IconPickerDialog.cpp b/gui/IconPickerDialog.cpp index 2dd80292..ebacf87c 100644 --- a/gui/IconPickerDialog.cpp +++ b/gui/IconPickerDialog.cpp @@ -3,6 +3,7 @@ #include "instancedelegate.h" #include "ui_IconPickerDialog.h" #include "logic/lists/IconList.h" +#include "gui/platform.h" #include <QKeyEvent> #include <QPushButton> #include <QFileDialog> @@ -11,6 +12,7 @@ IconPickerDialog::IconPickerDialog(QWidget *parent) : QDialog(parent), ui(new Ui::IconPickerDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); setWindowModality(Qt::WindowModal); @@ -39,10 +41,10 @@ IconPickerDialog::IconPickerDialog(QWidget *parent) : contentsWidget->installEventFilter(this); - contentsWidget->setModel(MMC->icons()); + contentsWidget->setModel(MMC->icons().get()); - auto buttonAdd = ui->buttonBox->addButton("Add Icon",QDialogButtonBox::ResetRole); - auto buttonRemove = ui->buttonBox->addButton("Remove Icon",QDialogButtonBox::ResetRole); + auto buttonAdd = ui->buttonBox->addButton(tr("Add Icon"),QDialogButtonBox::ResetRole); + auto buttonRemove = ui->buttonBox->addButton(tr("Remove Icon"),QDialogButtonBox::ResetRole); connect(buttonAdd,SIGNAL(clicked(bool)),SLOT(addNewIcon())); @@ -87,7 +89,10 @@ bool IconPickerDialog::eventFilter ( QObject* obj, QEvent* evt) void IconPickerDialog::addNewIcon() { - QStringList fileNames = QFileDialog::getOpenFileNames(this, "Select Icons", QString(), "Icons (*.png *.jpg *.jpeg)"); + //: The title of the select icons open file dialog + QString selectIcons = tr("Select Icons"); + //: The type of icon files + QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), tr("Icons") + "(*.png *.jpg *.jpeg)"); MMC->icons()->installIcons(fileNames); } @@ -116,7 +121,7 @@ void IconPickerDialog::selectionChanged ( QItemSelection selected, QItemSelectio int IconPickerDialog::exec ( QString selection ) { - IconList * list = MMC->icons(); + auto list = MMC->icons(); auto contentsWidget = ui->iconView; selectedIconKey = selection; diff --git a/gui/LegacyModEditDialog.cpp b/gui/LegacyModEditDialog.cpp index 616fc050..b230193a 100644 --- a/gui/LegacyModEditDialog.cpp +++ b/gui/LegacyModEditDialog.cpp @@ -3,7 +3,7 @@ * 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 @@ -13,59 +13,74 @@ * limitations under the License. */ +#include "MultiMC.h" #include "LegacyModEditDialog.h" #include "ModEditDialogCommon.h" +#include "versionselectdialog.h" +#include "ProgressDialog.h" #include "ui_LegacyModEditDialog.h" -#include <logic/ModList.h> +#include "logic/ModList.h" +#include "logic/lists/ForgeVersionList.h" +#include "gui/platform.h" + #include <pathutils.h> #include <QFileDialog> +//#include <QMessageBox> #include <QDebug> #include <QEvent> #include <QKeyEvent> -LegacyModEditDialog::LegacyModEditDialog( LegacyInstance* inst, QWidget* parent ) : - m_inst(inst), - QDialog(parent), - ui(new Ui::LegacyModEditDialog) +LegacyModEditDialog::LegacyModEditDialog(LegacyInstance *inst, QWidget *parent) + : m_inst(inst), QDialog(parent), ui(new Ui::LegacyModEditDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); - + // Jar mods { ensureFolderPathExists(m_inst->jarModsDir()); m_jarmods = m_inst->jarModList(); - ui->jarModsTreeView->setModel(m_jarmods.data()); + ui->jarModsTreeView->setModel(m_jarmods.get()); #ifndef Q_OS_LINUX // FIXME: internal DnD causes segfaults later ui->jarModsTreeView->setDragDropMode(QAbstractItemView::DragDrop); // FIXME: DnD is glitched with contiguous (we move only first item in selection) ui->jarModsTreeView->setSelectionMode(QAbstractItemView::SingleSelection); #endif - ui->jarModsTreeView->installEventFilter( this ); + ui->jarModsTreeView->installEventFilter(this); m_jarmods->startWatching(); + auto smodel = ui->jarModsTreeView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(jarCurrent(QModelIndex, QModelIndex))); } // Core mods { ensureFolderPathExists(m_inst->coreModsDir()); m_coremods = m_inst->coreModList(); - ui->coreModsTreeView->setModel(m_coremods.data()); - ui->coreModsTreeView->installEventFilter( this ); + ui->coreModsTreeView->setModel(m_coremods.get()); + ui->coreModsTreeView->installEventFilter(this); m_coremods->startWatching(); + auto smodel = ui->coreModsTreeView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(coreCurrent(QModelIndex, QModelIndex))); } // Loader mods { ensureFolderPathExists(m_inst->loaderModsDir()); m_mods = m_inst->loaderModList(); - ui->loaderModTreeView->setModel(m_mods.data()); - ui->loaderModTreeView->installEventFilter( this ); + ui->loaderModTreeView->setModel(m_mods.get()); + ui->loaderModTreeView->installEventFilter(this); m_mods->startWatching(); + auto smodel = ui->loaderModTreeView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(loaderCurrent(QModelIndex, QModelIndex))); } // texture packs { ensureFolderPathExists(m_inst->texturePacksDir()); m_texturepacks = m_inst->texturePackList(); - ui->texPackTreeView->setModel(m_texturepacks.data()); - ui->texPackTreeView->installEventFilter( this ); + ui->texPackTreeView->setModel(m_texturepacks.get()); + ui->texPackTreeView->installEventFilter(this); m_texturepacks->startWatching(); } } @@ -79,112 +94,111 @@ LegacyModEditDialog::~LegacyModEditDialog() delete ui; } -bool LegacyModEditDialog::coreListFilter ( QKeyEvent* keyEvent ) +bool LegacyModEditDialog::coreListFilter(QKeyEvent *keyEvent) { - switch(keyEvent->key()) + switch (keyEvent->key()) { - case Qt::Key_Delete: - on_rmCoreBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addCoreBtn_clicked(); - return true; - default: - break; + case Qt::Key_Delete: + on_rmCoreBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addCoreBtn_clicked(); + return true; + default: + break; } - return QDialog::eventFilter( ui->coreModsTreeView, keyEvent ); + return QDialog::eventFilter(ui->coreModsTreeView, keyEvent); } -bool LegacyModEditDialog::jarListFilter ( QKeyEvent* keyEvent ) +bool LegacyModEditDialog::jarListFilter(QKeyEvent *keyEvent) { - switch(keyEvent->key()) + switch (keyEvent->key()) + { + case Qt::Key_Up: { - case Qt::Key_Up: + if (keyEvent->modifiers() & Qt::ControlModifier) { - if(keyEvent->modifiers() & Qt::ControlModifier) - { - on_moveJarUpBtn_clicked(); - return true; - } - break; + on_moveJarUpBtn_clicked(); + return true; } - case Qt::Key_Down: + break; + } + case Qt::Key_Down: + { + if (keyEvent->modifiers() & Qt::ControlModifier) { - if(keyEvent->modifiers() & Qt::ControlModifier) - { - on_moveJarDownBtn_clicked(); - return true; - } - break; - } - case Qt::Key_Delete: - on_rmJarBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addJarBtn_clicked(); + on_moveJarDownBtn_clicked(); return true; - default: - break; + } + break; + } + case Qt::Key_Delete: + on_rmJarBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addJarBtn_clicked(); + return true; + default: + break; } - return QDialog::eventFilter( ui->jarModsTreeView, keyEvent ); + return QDialog::eventFilter(ui->jarModsTreeView, keyEvent); } -bool LegacyModEditDialog::loaderListFilter ( QKeyEvent* keyEvent ) +bool LegacyModEditDialog::loaderListFilter(QKeyEvent *keyEvent) { - switch(keyEvent->key()) + switch (keyEvent->key()) { - case Qt::Key_Delete: - on_rmModBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addModBtn_clicked(); - return true; - default: - break; + case Qt::Key_Delete: + on_rmModBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addModBtn_clicked(); + return true; + default: + break; } - return QDialog::eventFilter( ui->loaderModTreeView, keyEvent ); + return QDialog::eventFilter(ui->loaderModTreeView, keyEvent); } -bool LegacyModEditDialog::texturePackListFilter ( QKeyEvent* keyEvent ) +bool LegacyModEditDialog::texturePackListFilter(QKeyEvent *keyEvent) { - switch(keyEvent->key()) + switch (keyEvent->key()) { - case Qt::Key_Delete: - on_rmTexPackBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addTexPackBtn_clicked(); - return true; - default: - break; + case Qt::Key_Delete: + on_rmTexPackBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addTexPackBtn_clicked(); + return true; + default: + break; } - return QDialog::eventFilter( ui->texPackTreeView, keyEvent ); + return QDialog::eventFilter(ui->texPackTreeView, keyEvent); } - -bool LegacyModEditDialog::eventFilter ( QObject* obj, QEvent* ev ) +bool LegacyModEditDialog::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() != QEvent::KeyPress) { - return QDialog::eventFilter( obj, ev ); + return QDialog::eventFilter(obj, ev); } - QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev); - if(obj == ui->jarModsTreeView) + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); + if (obj == ui->jarModsTreeView) return jarListFilter(keyEvent); - if(obj == ui->coreModsTreeView) + if (obj == ui->coreModsTreeView) return coreListFilter(keyEvent); - if(obj == ui->loaderModTreeView) + if (obj == ui->loaderModTreeView) return loaderListFilter(keyEvent); - if(obj == ui->texPackTreeView) + if (obj == ui->texPackTreeView) return texturePackListFilter(keyEvent); - return QDialog::eventFilter( obj, ev ); + return QDialog::eventFilter(obj, ev); } - void LegacyModEditDialog::on_addCoreBtn_clicked() { - QStringList fileNames = QFileDialog::getOpenFileNames(this, "Select Core Mods"); - for(auto filename:fileNames) + //: Title of core mod selection dialog + QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Core Mods")); + for (auto filename : fileNames) { m_coremods->stopWatching(); m_coremods->installMod(QFileInfo(filename)); @@ -193,12 +207,45 @@ void LegacyModEditDialog::on_addCoreBtn_clicked() } void LegacyModEditDialog::on_addForgeBtn_clicked() { - + VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); + vselect.setFilter(1, m_inst->intendedVersionId()); + if (vselect.exec() && vselect.selectedVersion()) + { + ForgeVersionPtr forge = + std::dynamic_pointer_cast<ForgeVersion>(vselect.selectedVersion()); + if (!forge) + return; + auto entry = MMC->metacache()->resolveEntry("minecraftforge", forge->filename); + if (entry->stale) + { + DownloadJob *fjob = new DownloadJob("Forge download"); + fjob->addCacheDownload(forge->universal_url, entry); + ProgressDialog dlg(this); + dlg.exec(fjob); + if (dlg.result() == QDialog::Accepted) + { + m_jarmods->stopWatching(); + m_jarmods->installMod(QFileInfo(entry->getFullPath())); + m_jarmods->startWatching(); + } + else + { + // failed to download forge :/ + } + } + else + { + m_jarmods->stopWatching(); + m_jarmods->installMod(QFileInfo(entry->getFullPath())); + m_jarmods->startWatching(); + } + } } void LegacyModEditDialog::on_addJarBtn_clicked() { - QStringList fileNames = QFileDialog::getOpenFileNames(this, "Select Jar Mods"); - for(auto filename:fileNames) + //: Title of jar mod selection dialog + QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Jar Mods")); + for (auto filename : fileNames) { m_jarmods->stopWatching(); m_jarmods->installMod(QFileInfo(filename)); @@ -207,8 +254,9 @@ void LegacyModEditDialog::on_addJarBtn_clicked() } void LegacyModEditDialog::on_addModBtn_clicked() { - QStringList fileNames = QFileDialog::getOpenFileNames(this, "Select Loader Mods"); - for(auto filename:fileNames) + //: Title of regular mod selection dialog + QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Loader Mods")); + for (auto filename : fileNames) { m_mods->stopWatching(); m_mods->installMod(QFileInfo(filename)); @@ -217,8 +265,9 @@ void LegacyModEditDialog::on_addModBtn_clicked() } void LegacyModEditDialog::on_addTexPackBtn_clicked() { - QStringList fileNames = QFileDialog::getOpenFileNames(this, "Select Texture Packs"); - for(auto filename:fileNames) + //: Title of texture pack selection dialog + QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Texture Packs")); + for (auto filename : fileNames) { m_texturepacks->stopWatching(); m_texturepacks->installMod(QFileInfo(filename)); @@ -230,8 +279,8 @@ void LegacyModEditDialog::on_moveJarDownBtn_clicked() { int first, last; auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); - - if(!lastfirst(list, first, last)) + + if (!lastfirst(list, first, last)) return; m_jarmods->moveModsDown(first, last); @@ -240,8 +289,8 @@ void LegacyModEditDialog::on_moveJarUpBtn_clicked() { int first, last; auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); - - if(!lastfirst(list, first, last)) + + if (!lastfirst(list, first, last)) return; m_jarmods->moveModsUp(first, last); } @@ -249,8 +298,8 @@ void LegacyModEditDialog::on_rmCoreBtn_clicked() { int first, last; auto list = ui->coreModsTreeView->selectionModel()->selectedRows(); - - if(!lastfirst(list, first, last)) + + if (!lastfirst(list, first, last)) return; m_coremods->stopWatching(); m_coremods->deleteMods(first, last); @@ -260,8 +309,8 @@ void LegacyModEditDialog::on_rmJarBtn_clicked() { int first, last; auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); - - if(!lastfirst(list, first, last)) + + if (!lastfirst(list, first, last)) return; m_jarmods->stopWatching(); m_jarmods->deleteMods(first, last); @@ -271,8 +320,8 @@ void LegacyModEditDialog::on_rmModBtn_clicked() { int first, last; auto list = ui->loaderModTreeView->selectionModel()->selectedRows(); - - if(!lastfirst(list, first, last)) + + if (!lastfirst(list, first, last)) return; m_mods->stopWatching(); m_mods->deleteMods(first, last); @@ -282,8 +331,8 @@ void LegacyModEditDialog::on_rmTexPackBtn_clicked() { int first, last; auto list = ui->texPackTreeView->selectionModel()->selectedRows(); - - if(!lastfirst(list, first, last)) + + if (!lastfirst(list, first, last)) return; m_texturepacks->stopWatching(); m_texturepacks->deleteMods(first, last); @@ -302,8 +351,43 @@ void LegacyModEditDialog::on_viewTexPackBtn_clicked() openDirInDefaultProgram(m_inst->texturePacksDir(), true); } - void LegacyModEditDialog::on_buttonBox_rejected() { close(); -}
\ No newline at end of file +} + +void LegacyModEditDialog::jarCurrent(QModelIndex current, QModelIndex previous) +{ + if(!current.isValid()) + { + ui->jarMIFrame->clear(); + return; + } + int row = current.row(); + Mod &m = m_jarmods->operator[](row); + ui->jarMIFrame->updateWithMod(m); +} + +void LegacyModEditDialog::coreCurrent(QModelIndex current, QModelIndex previous) +{ + if(!current.isValid()) + { + ui->coreMIFrame->clear(); + return; + } + int row = current.row(); + Mod &m = m_coremods->operator[](row); + ui->coreMIFrame->updateWithMod(m); +} + +void LegacyModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous) +{ + if(!current.isValid()) + { + ui->loaderMIFrame->clear(); + return; + } + int row = current.row(); + Mod &m = m_mods->operator[](row); + ui->loaderMIFrame->updateWithMod(m); +} diff --git a/gui/LegacyModEditDialog.h b/gui/LegacyModEditDialog.h index bc9ebac0..fc3ea1e6 100644 --- a/gui/LegacyModEditDialog.h +++ b/gui/LegacyModEditDialog.h @@ -3,7 +3,7 @@ * 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 @@ -17,51 +17,62 @@ #include <QDialog> #include "logic/LegacyInstance.h" +#include <logic/net/DownloadJob.h> -namespace Ui { +namespace Ui +{ class LegacyModEditDialog; } class LegacyModEditDialog : public QDialog { Q_OBJECT - + public: - explicit LegacyModEditDialog(LegacyInstance* inst, QWidget *parent = 0); + explicit LegacyModEditDialog(LegacyInstance *inst, QWidget *parent = 0); ~LegacyModEditDialog(); - -private slots: - + +private +slots: + void on_addJarBtn_clicked(); void on_rmJarBtn_clicked(); void on_addForgeBtn_clicked(); void on_moveJarUpBtn_clicked(); void on_moveJarDownBtn_clicked(); - + void on_addCoreBtn_clicked(); void on_rmCoreBtn_clicked(); void on_viewCoreBtn_clicked(); - + void on_addModBtn_clicked(); void on_rmModBtn_clicked(); void on_viewModBtn_clicked(); - + void on_addTexPackBtn_clicked(); void on_rmTexPackBtn_clicked(); void on_viewTexPackBtn_clicked(); + // Questionable: SettingsDialog doesn't need this for some reason? void on_buttonBox_rejected(); + + void jarCurrent(QModelIndex current, QModelIndex previous); + void coreCurrent(QModelIndex current, QModelIndex previous); + void loaderCurrent(QModelIndex current, QModelIndex previous); + protected: bool eventFilter(QObject *obj, QEvent *ev); - bool jarListFilter( QKeyEvent* ev ); - bool coreListFilter( QKeyEvent* ev ); - bool loaderListFilter( QKeyEvent* ev ); - bool texturePackListFilter( QKeyEvent* ev ); + bool jarListFilter(QKeyEvent *ev); + bool coreListFilter(QKeyEvent *ev); + bool loaderListFilter(QKeyEvent *ev); + bool texturePackListFilter(QKeyEvent *ev); + private: Ui::LegacyModEditDialog *ui; - QSharedPointer<ModList> m_mods; - QSharedPointer<ModList> m_coremods; - QSharedPointer<ModList> m_jarmods; - QSharedPointer<ModList> m_texturepacks; - LegacyInstance * m_inst; + std::shared_ptr<ModList> m_mods; + std::shared_ptr<ModList> m_coremods; + std::shared_ptr<ModList> m_jarmods; + std::shared_ptr<ModList> m_texturepacks; + LegacyInstance *m_inst; + DownloadJobPtr forgeJob; }; diff --git a/gui/LegacyModEditDialog.ui b/gui/LegacyModEditDialog.ui index bd147c85..bb0d9ef2 100644 --- a/gui/LegacyModEditDialog.ui +++ b/gui/LegacyModEditDialog.ui @@ -23,179 +23,215 @@ <attribute name="title"> <string>Jar Mods</string> </attribute> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QVBoxLayout" name="verticalLayout_2"> <item> - <widget class="ModListView" name="jarModsTreeView"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOn</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="jarModsButtonBox"> + <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <widget class="QPushButton" name="addJarBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmJarBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="addForgeBtn"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>MCForge</string> - </property> - </widget> - </item> - <item> - <spacer name="jarModsButtonSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> + <widget class="ModListView" name="jarModsTreeView"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="moveJarUpBtn"> - <property name="text"> - <string>Move &Up</string> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> </property> </widget> </item> <item> - <widget class="QPushButton" name="moveJarDownBtn"> - <property name="text"> - <string>Move &Down</string> - </property> - </widget> + <layout class="QVBoxLayout" name="jarModsButtonBox"> + <item> + <widget class="QPushButton" name="addJarBtn"> + <property name="text"> + <string>&Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="rmJarBtn"> + <property name="text"> + <string>&Remove</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="addForgeBtn"> + <property name="text"> + <string>MCForge</string> + </property> + </widget> + </item> + <item> + <spacer name="jarModsButtonSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="moveJarUpBtn"> + <property name="text"> + <string>Move &Up</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveJarDownBtn"> + <property name="text"> + <string>Move &Down</string> + </property> + </widget> + </item> + </layout> </item> </layout> </item> + <item> + <widget class="MCModInfoFrame" name="jarMIFrame"> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="coreTab"> <attribute name="title"> <string>Core Mods</string> </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QVBoxLayout" name="verticalLayout_3"> <item> - <widget class="ModListView" name="coreModsTreeView"> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="coreModsButtonBox"> - <item> - <widget class="QPushButton" name="addCoreBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> - <widget class="QPushButton" name="rmCoreBtn"> - <property name="text"> - <string>&Remove</string> + <widget class="ModListView" name="coreModsTreeView"> + <property name="dragDropMode"> + <enum>QAbstractItemView::DropOnly</enum> </property> </widget> </item> <item> - <spacer name="coreModsButtonSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="viewCoreBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> + <layout class="QVBoxLayout" name="coreModsButtonBox"> + <item> + <widget class="QPushButton" name="addCoreBtn"> + <property name="text"> + <string>&Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="rmCoreBtn"> + <property name="text"> + <string>&Remove</string> + </property> + </widget> + </item> + <item> + <spacer name="coreModsButtonSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="viewCoreBtn"> + <property name="text"> + <string>&View Folder</string> + </property> + </widget> + </item> + </layout> </item> </layout> </item> + <item> + <widget class="MCModInfoFrame" name="coreMIFrame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="modTab"> <attribute name="title"> <string>Loader Mods</string> </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="ModListView" name="loaderModTreeView"> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> + <layout class="QVBoxLayout" name="verticalLayout_4"> <item> - <layout class="QVBoxLayout" name="mlModsButtonBox"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> - <widget class="QPushButton" name="addModBtn"> - <property name="text"> - <string>&Add</string> + <widget class="ModListView" name="loaderModTreeView"> + <property name="acceptDrops"> + <bool>true</bool> </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmModBtn"> - <property name="text"> - <string>&Remove</string> + <property name="dragDropMode"> + <enum>QAbstractItemView::DropOnly</enum> </property> </widget> </item> <item> - <spacer name="mlModsButtonSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="viewModBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> + <layout class="QVBoxLayout" name="mlModsButtonBox"> + <item> + <widget class="QPushButton" name="addModBtn"> + <property name="text"> + <string>&Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="rmModBtn"> + <property name="text"> + <string>&Remove</string> + </property> + </widget> + </item> + <item> + <spacer name="mlModsButtonSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="viewModBtn"> + <property name="text"> + <string>&View Folder</string> + </property> + </widget> + </item> + </layout> </item> </layout> </item> + <item> + <widget class="MCModInfoFrame" name="loaderMIFrame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="texPackTab"> @@ -273,6 +309,12 @@ <extends>QTreeView</extends> <header>gui/ModListView.h</header> </customwidget> + <customwidget> + <class>MCModInfoFrame</class> + <extends>QFrame</extends> + <header>gui/MCModInfoFrame.h</header> + <container>1</container> + </customwidget> </customwidgets> <resources/> <connections/> diff --git a/gui/MCModInfoFrame.cpp b/gui/MCModInfoFrame.cpp new file mode 100644 index 00000000..b3f4ca5b --- /dev/null +++ b/gui/MCModInfoFrame.cpp @@ -0,0 +1,110 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MCModInfoFrame.h" +#include "ui_MCModInfoFrame.h" +#include <QMessageBox> +#include <QtGui> +void MCModInfoFrame::updateWithMod(Mod &m) +{ + if(m.type() == m.MOD_FOLDER) + { + clear(); + return; + } + + QString text = ""; + QString name = ""; + if(m.name().isEmpty()) name = m.id(); + else name = m.name(); + + if(m.homeurl().isEmpty()) text = name; + else text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>"; + if(!m.authors().isEmpty()) text += " by " + m.authors(); + + setModText(text); + + if(m.description().isEmpty()) + { + setModDescription(tr("No description provided in mcmod.info")); + } + else + { + setModDescription(m.description()); + } +} + +void MCModInfoFrame::clear() +{ + setModText(tr("Select a mod to view title and authors...")); + setModDescription(tr("Select a mod to view description...")); +} + +MCModInfoFrame::MCModInfoFrame(QWidget *parent) : + QFrame(parent), + ui(new Ui::MCModInfoFrame) +{ + ui->setupUi(this); +} + +MCModInfoFrame::~MCModInfoFrame() +{ + delete ui; +} + +void MCModInfoFrame::setModText(QString text) +{ + ui->label_ModText->setText(text); +} + +void MCModInfoFrame::setModDescription(QString text) +{ + ui->label_ModDescription->setToolTip(""); + QString intermediatetext = text.trimmed(); + bool prev(false); + QChar rem('\n'); + QString finaltext; + finaltext.reserve(intermediatetext.size()); + foreach(const QChar& c, intermediatetext) + { + if(c == rem && prev){ + continue; + } + prev = c == rem; + finaltext += c; + } + QString labeltext; + labeltext.reserve(300); + if(finaltext.length() > 290) + { + ui->label_ModDescription->setOpenExternalLinks(false); + ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText); + desc = text; + labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>"); + QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler); + } + else + { + ui->label_ModDescription->setTextFormat(Qt::TextFormat::PlainText); + labeltext.append(finaltext); + } + ui->label_ModDescription->setText(labeltext); +} +void MCModInfoFrame::modDescEllipsisHandler(const QString &link) +{ + QMessageBox msgbox; + msgbox.setText(desc); + msgbox.exec(); +} diff --git a/gui/MCModInfoFrame.h b/gui/MCModInfoFrame.h new file mode 100644 index 00000000..54c5d674 --- /dev/null +++ b/gui/MCModInfoFrame.h @@ -0,0 +1,46 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QFrame> +#include "logic/Mod.h" + +namespace Ui +{ +class MCModInfoFrame; +} + +class MCModInfoFrame : public QFrame +{ + Q_OBJECT + +public: + explicit MCModInfoFrame(QWidget *parent = 0); + ~MCModInfoFrame(); + + void setModText(QString text); + void setModDescription(QString text); + + void updateWithMod(Mod &m); + void clear(); + +public slots: + void modDescEllipsisHandler(const QString& link ); + +private: + Ui::MCModInfoFrame *ui; + QString desc; +}; diff --git a/gui/MCModInfoFrame.ui b/gui/MCModInfoFrame.ui new file mode 100644 index 00000000..60e0a65c --- /dev/null +++ b/gui/MCModInfoFrame.ui @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MCModInfoFrame</class> + <widget class="QFrame" name="MCModInfoFrame"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>527</width> + <height>113</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>120</height> + </size> + </property> + <property name="windowTitle"> + <string>Frame</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="label_ModText"> + <property name="text"> + <string>Select a mod to view title and authors...</string> + </property> + <property name="textFormat"> + <enum>Qt::RichText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_ModDescription"> + <property name="text"> + <string>Select a mod to view description...</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/gui/ModEditDialogCommon.cpp b/gui/ModEditDialogCommon.cpp index 5da0a039..692ac0c4 100644 --- a/gui/ModEditDialogCommon.cpp +++ b/gui/ModEditDialogCommon.cpp @@ -1,17 +1,40 @@ #include "ModEditDialogCommon.h" - -bool lastfirst (QModelIndexList & list, int & first, int & last) +#include <QDesktopServices> +#include <QMessageBox> +#include <QString> +#include <QUrl> +bool lastfirst(QModelIndexList &list, int &first, int &last) { - if(!list.size()) + if (!list.size()) return false; first = last = list[0].row(); - for(auto item: list) + for (auto item : list) { int row = item.row(); - if(row < first) + if (row < first) first = row; - if(row > last) + if (row > last) last = row; } return true; -}
\ No newline at end of file +} + +void showWebsiteForMod(QWidget *parentDlg, Mod &m) +{ + QString url = m.homeurl(); + if (url.size()) + { + // catch the cases where the protocol is missing + if(!url.startsWith("http")) + { + url = "http://" + url; + } + QDesktopServices::openUrl(url); + } + else + { + QMessageBox::warning( + parentDlg, parentDlg->tr("How sad!"), + parentDlg->tr("The mod author didn't provide a website link for this mod.")); + } +} diff --git a/gui/ModEditDialogCommon.h b/gui/ModEditDialogCommon.h index a27a8a82..bc8e223f 100644 --- a/gui/ModEditDialogCommon.h +++ b/gui/ModEditDialogCommon.h @@ -1,4 +1,7 @@ #pragma once #include <QAbstractItemModel> +#include <logic/Mod.h> -bool lastfirst (QModelIndexList & list, int & first, int & last);
\ No newline at end of file +bool lastfirst (QModelIndexList & list, int & first, int & last); + +void showWebsiteForMod(QWidget * parentDlg, Mod& m);
\ No newline at end of file diff --git a/gui/ModListView.cpp b/gui/ModListView.cpp index 34bd4af2..1d0e834c 100644 --- a/gui/ModListView.cpp +++ b/gui/ModListView.cpp @@ -30,6 +30,7 @@ void ModListView::setModel ( QAbstractItemModel* model ) auto head = header(); head->setStretchLastSection(false); head->setSectionResizeMode(0, QHeaderView::Stretch); - head->setSectionResizeMode(1, QHeaderView::ResizeToContents); + for(int i = 1; i < head->count(); i++) + head->setSectionResizeMode(i, QHeaderView::ResizeToContents); dropIndicatorPosition(); } diff --git a/gui/ModListView.h b/gui/ModListView.h index a9408fe7..69a26755 100644 --- a/gui/ModListView.h +++ b/gui/ModListView.h @@ -9,4 +9,5 @@ class ModListView: public QTreeView public: explicit ModListView ( QWidget* parent = 0 ); virtual void setModel ( QAbstractItemModel* model ); -};
\ No newline at end of file + +}; diff --git a/gui/OneSixModEditDialog.cpp b/gui/OneSixModEditDialog.cpp index 8e738fa1..88738938 100644 --- a/gui/OneSixModEditDialog.cpp +++ b/gui/OneSixModEditDialog.cpp @@ -1,9 +1,9 @@ /* Copyright 2013 MultiMC Contributors - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -12,41 +12,66 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +#include "MultiMC.h" #include "OneSixModEditDialog.h" #include "ModEditDialogCommon.h" #include "ui_OneSixModEditDialog.h" -#include <logic/ModList.h> +#include "logic/ModList.h" +#include "logic/OneSixVersion.h" +#include "logic/EnabledItemFilter.h" +#include "logic/lists/ForgeVersionList.h" +#include "logic/ForgeInstaller.h" +#include "gui/versionselectdialog.h" +#include "gui/platform.h" +#include "ProgressDialog.h" + #include <pathutils.h> #include <QFileDialog> +#include <QMessageBox> #include <QDebug> #include <QEvent> #include <QKeyEvent> +#include <QDesktopServices> -OneSixModEditDialog::OneSixModEditDialog(OneSixInstance * inst, QWidget *parent): - m_inst(inst), - QDialog(parent), - ui(new Ui::OneSixModEditDialog) +OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent) + : m_inst(inst), QDialog(parent), ui(new Ui::OneSixModEditDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); - //TODO: libraries! + // libraries! + + m_version = m_inst->getFullVersion(); + if (m_version) { - // yeah... here be the real dragons. + main_model = new EnabledItemFilter(this); + main_model->setActive(true); + main_model->setSourceModel(m_version.get()); + ui->libraryTreeView->setModel(main_model); + ui->libraryTreeView->installEventFilter(this); + ui->mainClassEdit->setText(m_version->mainClass); + updateVersionControls(); + } + else + { + disableVersionControls(); } // Loader mods { ensureFolderPathExists(m_inst->loaderModsDir()); m_mods = m_inst->loaderModList(); - ui->loaderModTreeView->setModel(m_mods.data()); - ui->loaderModTreeView->installEventFilter( this ); + ui->loaderModTreeView->setModel(m_mods.get()); + ui->loaderModTreeView->installEventFilter(this); m_mods->startWatching(); + auto smodel = ui->loaderModTreeView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(loaderCurrent(QModelIndex,QModelIndex))); } // resource packs { ensureFolderPathExists(m_inst->resourcePacksDir()); m_resourcepacks = m_inst->resourcePackList(); - ui->resPackTreeView->setModel(m_resourcepacks.data()); - ui->resPackTreeView->installEventFilter( this ); + ui->resPackTreeView->setModel(m_resourcepacks.get()); + ui->resPackTreeView->installEventFilter(this); m_resourcepacks->startWatching(); } } @@ -58,51 +83,165 @@ OneSixModEditDialog::~OneSixModEditDialog() delete ui; } -bool OneSixModEditDialog::loaderListFilter ( QKeyEvent* keyEvent ) +void OneSixModEditDialog::updateVersionControls() +{ + bool customVersion = m_inst->versionIsCustom(); + ui->customizeBtn->setEnabled(!customVersion); + ui->revertBtn->setEnabled(customVersion); + ui->forgeBtn->setEnabled(true); +} + +void OneSixModEditDialog::disableVersionControls() { - switch(keyEvent->key()) + ui->customizeBtn->setEnabled(false); + ui->revertBtn->setEnabled(false); + ui->forgeBtn->setEnabled(false); +} + +void OneSixModEditDialog::on_customizeBtn_clicked() +{ + if (m_inst->customizeVersion()) { - case Qt::Key_Delete: - on_rmModBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addModBtn_clicked(); - return true; - default: - break; + m_version = m_inst->getFullVersion(); + main_model->setSourceModel(m_version.get()); + updateVersionControls(); } - return QDialog::eventFilter( ui->loaderModTreeView, keyEvent ); } -bool OneSixModEditDialog::resourcePackListFilter ( QKeyEvent* keyEvent ) +void OneSixModEditDialog::on_revertBtn_clicked() { - switch(keyEvent->key()) + auto reply = QMessageBox::question( + this, tr("Revert?"), tr("Do you want to revert the " + "version of this instance to its original configuration?"), + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) { - case Qt::Key_Delete: - on_rmResPackBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addResPackBtn_clicked(); - return true; - default: - break; + if (m_inst->revertCustomVersion()) + { + m_version = m_inst->getFullVersion(); + main_model->setSourceModel(m_version.get()); + updateVersionControls(); + } } - return QDialog::eventFilter( ui->resPackTreeView, keyEvent ); } +void OneSixModEditDialog::on_forgeBtn_clicked() +{ + VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); + vselect.setFilter(1, m_inst->currentVersionId()); + if (vselect.exec() && vselect.selectedVersion()) + { + if (m_inst->versionIsCustom()) + { + auto reply = QMessageBox::question( + this, tr("Revert?"), + tr("This will revert any " + "changes you did to the version up to this point. Is that " + "OK?"), + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) + { + m_inst->revertCustomVersion(); + m_inst->customizeVersion(); + { + m_version = m_inst->getFullVersion(); + main_model->setSourceModel(m_version.get()); + updateVersionControls(); + } + } + else + return; + } + else + { + m_inst->customizeVersion(); + m_version = m_inst->getFullVersion(); + main_model->setSourceModel(m_version.get()); + updateVersionControls(); + } + ForgeVersionPtr forgeVersion = + std::dynamic_pointer_cast<ForgeVersion>(vselect.selectedVersion()); + if (!forgeVersion) + return; + auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); + if (entry->stale) + { + DownloadJob *fjob = new DownloadJob("Forge download"); + fjob->addCacheDownload(forgeVersion->installer_url, entry); + ProgressDialog dlg(this); + dlg.exec(fjob); + if (dlg.result() == QDialog::Accepted) + { + // install + QString forgePath = entry->getFullPath(); + ForgeInstaller forge(forgePath, forgeVersion->universal_url); + if (!forge.apply(m_version)) + { + // failure notice + } + } + else + { + // failed to download forge :/ + } + } + else + { + // install + QString forgePath = entry->getFullPath(); + ForgeInstaller forge(forgePath, forgeVersion->universal_url); + if (!forge.apply(m_version)) + { + // failure notice + } + } + } +} -bool OneSixModEditDialog::eventFilter ( QObject* obj, QEvent* ev ) +bool OneSixModEditDialog::loaderListFilter(QKeyEvent *keyEvent) +{ + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_rmModBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addModBtn_clicked(); + return true; + default: + break; + } + return QDialog::eventFilter(ui->loaderModTreeView, keyEvent); +} + +bool OneSixModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent) +{ + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_rmResPackBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addResPackBtn_clicked(); + return true; + default: + break; + } + return QDialog::eventFilter(ui->resPackTreeView, keyEvent); +} + +bool OneSixModEditDialog::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() != QEvent::KeyPress) { - return QDialog::eventFilter( obj, ev ); + return QDialog::eventFilter(obj, ev); } - QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev); - if(obj == ui->loaderModTreeView) + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); + if (obj == ui->loaderModTreeView) return loaderListFilter(keyEvent); - if(obj == ui->resPackTreeView) + if (obj == ui->resPackTreeView) return resourcePackListFilter(keyEvent); - return QDialog::eventFilter( obj, ev ); + return QDialog::eventFilter(obj, ev); } void OneSixModEditDialog::on_buttonBox_rejected() @@ -112,8 +251,9 @@ void OneSixModEditDialog::on_buttonBox_rejected() void OneSixModEditDialog::on_addModBtn_clicked() { - QStringList fileNames = QFileDialog::getOpenFileNames(this, "Select Loader Mods"); - for(auto filename:fileNames) + QStringList fileNames = QFileDialog::getOpenFileNames( + this, QApplication::translate("LegacyModEditDialog", "Select Loader Mods")); + for (auto filename : fileNames) { m_mods->stopWatching(); m_mods->installMod(QFileInfo(filename)); @@ -124,8 +264,8 @@ void OneSixModEditDialog::on_rmModBtn_clicked() { int first, last; auto list = ui->loaderModTreeView->selectionModel()->selectedRows(); - - if(!lastfirst(list, first, last)) + + if (!lastfirst(list, first, last)) return; m_mods->stopWatching(); m_mods->deleteMods(first, last); @@ -136,11 +276,11 @@ void OneSixModEditDialog::on_viewModBtn_clicked() openDirInDefaultProgram(m_inst->loaderModsDir(), true); } - void OneSixModEditDialog::on_addResPackBtn_clicked() { - QStringList fileNames = QFileDialog::getOpenFileNames(this, "Select Resource Packs"); - for(auto filename:fileNames) + QStringList fileNames = QFileDialog::getOpenFileNames( + this, QApplication::translate("LegacyModEditDialog", "Select Resource Packs")); + for (auto filename : fileNames) { m_resourcepacks->stopWatching(); m_resourcepacks->installMod(QFileInfo(filename)); @@ -151,8 +291,8 @@ void OneSixModEditDialog::on_rmResPackBtn_clicked() { int first, last; auto list = ui->resPackTreeView->selectionModel()->selectedRows(); - - if(!lastfirst(list, first, last)) + + if (!lastfirst(list, first, last)) return; m_resourcepacks->stopWatching(); m_resourcepacks->deleteMods(first, last); @@ -163,3 +303,14 @@ void OneSixModEditDialog::on_viewResPackBtn_clicked() openDirInDefaultProgram(m_inst->resourcePacksDir(), true); } +void OneSixModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous) +{ + if(!current.isValid()) + { + ui->frame->clear(); + return; + } + int row = current.row(); + Mod &m = m_mods->operator[](row); + ui->frame->updateWithMod(m); +} diff --git a/gui/OneSixModEditDialog.h b/gui/OneSixModEditDialog.h index 3430bd26..5376e526 100644 --- a/gui/OneSixModEditDialog.h +++ b/gui/OneSixModEditDialog.h @@ -1,9 +1,9 @@ /* Copyright 2013 MultiMC Contributors - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -18,35 +18,50 @@ #include <logic/OneSixInstance.h> -namespace Ui { - class OneSixModEditDialog; +class EnabledItemFilter; +namespace Ui +{ +class OneSixModEditDialog; } class OneSixModEditDialog : public QDialog { Q_OBJECT - + public: - explicit OneSixModEditDialog(OneSixInstance* inst, QWidget *parent = 0); + explicit OneSixModEditDialog(OneSixInstance *inst, QWidget *parent = 0); virtual ~OneSixModEditDialog(); - -private slots: + +private +slots: void on_addModBtn_clicked(); void on_rmModBtn_clicked(); void on_viewModBtn_clicked(); - + void on_addResPackBtn_clicked(); void on_rmResPackBtn_clicked(); void on_viewResPackBtn_clicked(); // Questionable: SettingsDialog doesn't need this for some reason? void on_buttonBox_rejected(); + void on_forgeBtn_clicked(); + void on_customizeBtn_clicked(); + void on_revertBtn_clicked(); + void updateVersionControls(); + void disableVersionControls(); + protected: bool eventFilter(QObject *obj, QEvent *ev); - bool loaderListFilter( QKeyEvent* ev ); - bool resourcePackListFilter( QKeyEvent* ev ); + bool loaderListFilter(QKeyEvent *ev); + bool resourcePackListFilter(QKeyEvent *ev); + private: Ui::OneSixModEditDialog *ui; - QSharedPointer<ModList> m_mods; - QSharedPointer<ModList> m_resourcepacks; - OneSixInstance * m_inst; + std::shared_ptr<OneSixVersion> m_version; + std::shared_ptr<ModList> m_mods; + std::shared_ptr<ModList> m_resourcepacks; + EnabledItemFilter *main_model; + OneSixInstance *m_inst; +public +slots: + void loaderCurrent(QModelIndex current, QModelIndex previous); }; diff --git a/gui/OneSixModEditDialog.ui b/gui/OneSixModEditDialog.ui index 3feca726..6d70200a 100644 --- a/gui/OneSixModEditDialog.ui +++ b/gui/OneSixModEditDialog.ui @@ -6,15 +6,15 @@ <rect> <x>0</x> <y>0</y> - <width>543</width> - <height>423</height> + <width>555</width> + <height>463</height> </rect> </property> <property name="windowTitle"> <string>Dialog</string> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> <widget class="QTabWidget" name="tabWidget"> <property name="enabled"> <bool>true</bool> @@ -26,64 +26,118 @@ </size> </property> <property name="currentIndex"> - <number>0</number> - </property> - <property name="elideMode"> - <enum>Qt::ElideNone</enum> - </property> - <property name="tabsClosable"> - <bool>false</bool> + <number>1</number> </property> <widget class="QWidget" name="libTab"> <attribute name="title"> - <string>Library</string> + <string>Version</string> </attribute> <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <widget class="ModListView" name="jarModsTreeView"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOn</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="modTab"> - <attribute name="title"> - <string>Loader Mods</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="ModListView" name="loaderModTreeView"> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> + <layout class="QVBoxLayout" name="verticalLayout_10"> + <item> + <widget class="ModListView" name="libraryTreeView"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Main Class:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="mainClassEdit"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> </item> <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QPushButton" name="forgeBtn"> + <property name="toolTip"> + <string>Replace any current custom version with Minecraft Forge</string> + </property> + <property name="text"> + <string>Install Forge</string> + </property> + </widget> + </item> <item> - <widget class="QPushButton" name="addModBtn"> + <widget class="QPushButton" name="customizeBtn"> + <property name="toolTip"> + <string>Create an customized copy of the base version</string> + </property> + <property name="text"> + <string>Customize</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="revertBtn"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Revert to original base version</string> + </property> + <property name="text"> + <string>Revert</string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="addLibraryBtn"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Add new libraries</string> + </property> <property name="text"> <string>&Add</string> </property> </widget> </item> <item> - <widget class="QPushButton" name="rmModBtn"> + <widget class="QPushButton" name="removeLibraryBtn"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Remove selected libraries</string> + </property> <property name="text"> <string>&Remove</string> </property> </widget> </item> <item> - <spacer name="verticalSpacer"> + <spacer name="verticalSpacer_7"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> @@ -95,15 +149,87 @@ </property> </spacer> </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="modTab"> + <attribute name="title"> + <string>Loader Mods</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> - <widget class="QPushButton" name="viewModBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="ModListView" name="loaderModTreeView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DropOnly</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPushButton" name="addModBtn"> + <property name="text"> + <string>&Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="rmModBtn"> + <property name="text"> + <string>&Remove</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="viewModBtn"> + <property name="text"> + <string>&View Folder</string> + </property> + </widget> + </item> + </layout> </item> </layout> </item> + <item> + <widget class="MCModInfoFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="resPackTab"> @@ -163,7 +289,7 @@ </widget> </widget> </item> - <item> + <item row="1" column="0"> <widget class="QDialogButtonBox" name="buttonBox"> <property name="autoFillBackground"> <bool>false</bool> @@ -181,6 +307,12 @@ <extends>QTreeView</extends> <header>gui/ModListView.h</header> </customwidget> + <customwidget> + <class>MCModInfoFrame</class> + <extends>QFrame</extends> + <header>gui/MCModInfoFrame.h</header> + <container>1</container> + </customwidget> </customwidgets> <resources/> <connections/> diff --git a/gui/taskdialog.cpp b/gui/ProgressDialog.cpp index 8c745b38..2e5251a0 100644 --- a/gui/taskdialog.cpp +++ b/gui/ProgressDialog.cpp @@ -13,88 +13,91 @@ * limitations under the License. */ -#include "taskdialog.h" -#include "ui_taskdialog.h" +#include "ProgressDialog.h" +#include "ui_ProgressDialog.h" #include <QKeyEvent> #include "logic/tasks/Task.h" +#include "gui/platform.h" -TaskDialog::TaskDialog(QWidget *parent) : +ProgressDialog::ProgressDialog(QWidget *parent) : QDialog(parent), - ui(new Ui::TaskDialog) + ui(new Ui::ProgressDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); updateSize(); - changeProgress(0); + changeProgress(0,100); } -TaskDialog::~TaskDialog() +ProgressDialog::~ProgressDialog() { delete ui; } -void TaskDialog::updateSize() +void ProgressDialog::updateSize() { resize(QSize(480, minimumSizeHint().height())); } -void TaskDialog::exec(Task *task) +int ProgressDialog::exec(ProgressProvider *task) { this->task = task; // Connect signals. connect(task, SIGNAL(started()), SLOT(onTaskStarted())); - connect(task, SIGNAL(failed(QString)), SLOT(onTaskEnded())); - connect(task, SIGNAL(succeeded()), SLOT(onTaskEnded())); - connect(task, SIGNAL(statusChanged(const QString&)), SLOT(changeStatus(const QString&))); - connect(task, SIGNAL(progressChanged(int)), SLOT(changeProgress(int))); + connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString))); + connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded())); + connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString&))); + connect(task, SIGNAL(progress(qint64,qint64)), SLOT(changeProgress(qint64,qint64))); // this makes sure that the task is started after the dialog is created - QMetaObject::invokeMethod(task, "startTask", Qt::QueuedConnection); - QDialog::exec(); + QMetaObject::invokeMethod(task, "start", Qt::QueuedConnection); + return QDialog::exec(); } -Task* TaskDialog::getTask() +ProgressProvider* ProgressDialog::getTask() { return task; } -void TaskDialog::onTaskStarted() +void ProgressDialog::onTaskStarted() { } -void TaskDialog::onTaskEnded() +void ProgressDialog::onTaskFailed(QString failure) { - close(); + reject(); } -void TaskDialog::changeStatus(const QString &status) +void ProgressDialog::onTaskSucceeded() +{ + accept(); +} + +void ProgressDialog::changeStatus(const QString &status) { ui->statusLabel->setText(status); updateSize(); } -void TaskDialog::changeProgress(int progress) +void ProgressDialog::changeProgress(qint64 current, qint64 total) { - if (progress < 0) - progress = 0; - else if (progress > 100) - progress = 100; - - ui->taskProgressBar->setValue(progress); + ui->taskProgressBar->setMaximum(total); + ui->taskProgressBar->setValue(current); } -void TaskDialog::keyPressEvent(QKeyEvent* e) +void ProgressDialog::keyPressEvent(QKeyEvent* e) { if (e->key() == Qt::Key_Escape) return; QDialog::keyPressEvent(e); } -void TaskDialog::closeEvent(QCloseEvent* e) +void ProgressDialog::closeEvent(QCloseEvent* e) { if (task && task->isRunning()) { diff --git a/gui/taskdialog.h b/gui/ProgressDialog.h index 3d31b7be..ac6bb412 100644 --- a/gui/taskdialog.h +++ b/gui/ProgressDialog.h @@ -18,32 +18,33 @@ #include <QDialog> -class Task; +class ProgressProvider; namespace Ui { -class TaskDialog; +class ProgressDialog; } -class TaskDialog : public QDialog +class ProgressDialog : public QDialog { Q_OBJECT public: - explicit TaskDialog(QWidget *parent = 0); - ~TaskDialog(); + explicit ProgressDialog(QWidget *parent = 0); + ~ProgressDialog(); void updateSize(); - void exec(Task* task); + int exec(ProgressProvider* task); - Task* getTask(); + ProgressProvider* getTask(); public slots: void onTaskStarted(); - void onTaskEnded(); + void onTaskFailed(QString failure); + void onTaskSucceeded(); void changeStatus(const QString& status); - void changeProgress(int progress); + void changeProgress(qint64 current, qint64 total); signals: @@ -53,9 +54,9 @@ protected: virtual void closeEvent(QCloseEvent* e); private: - Ui::TaskDialog *ui; + Ui::ProgressDialog *ui; - Task* task; + ProgressProvider* task; }; #endif // TASKDIALOG_H diff --git a/gui/taskdialog.ui b/gui/ProgressDialog.ui index 1cdf7978..a56d2a92 100644 --- a/gui/taskdialog.ui +++ b/gui/ProgressDialog.ui @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>TaskDialog</class> - <widget class="QDialog" name="TaskDialog"> + <class>ProgressDialog</class> + <widget class="QDialog" name="ProgressDialog"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> - <height>58</height> + <height>68</height> </rect> </property> <property name="minimumSize"> diff --git a/gui/aboutdialog.cpp b/gui/aboutdialog.cpp index 9792d4fe..7105446c 100644 --- a/gui/aboutdialog.cpp +++ b/gui/aboutdialog.cpp @@ -2,11 +2,13 @@ #include "ui_aboutdialog.h" #include <QIcon> #include <MultiMC.h> +#include "gui/platform.h" AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); ui->icon->setPixmap(QIcon(":/icons/multimc/scalable/apps/multimc.svg").pixmap(64)); diff --git a/gui/aboutdialog.ui b/gui/aboutdialog.ui index 6b8f906d..66b40036 100644 --- a/gui/aboutdialog.ui +++ b/gui/aboutdialog.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>450</width> - <height>400</height> + <height>429</height> </rect> </property> <property name="minimumSize"> @@ -58,6 +58,9 @@ <property name="text"> <string/> </property> + <property name="pixmap"> + <pixmap resource="../multimc.qrc">:/icons/multimc/scalable/apps/multimc.svg</pixmap> + </property> </widget> </item> <item> @@ -92,16 +95,13 @@ </item> <item> <widget class="QToolBox" name="toolBox"> - <property name="currentIndex"> - <number>0</number> - </property> <widget class="QWidget" name="aboutPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>432</width> - <height>153</height> + <height>191</height> </rect> </property> <attribute name="label"> @@ -111,7 +111,7 @@ <item> <widget class="QLabel" name="aboutLabel"> <property name="text"> - <string>MultiMC is a custom launcher that makes managing Minecraft easier by allowing you to have multiple installations of Minecraft at once.</string> + <string><html><head/><body><p>MultiMC is a custom launcher that makes managing Minecraft easier by allowing you to have multiple instances of Minecraft at once.</p></body></html></string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -145,7 +145,7 @@ </font> </property> <property name="text"> - <string><html><head/><body><p><a href="http://github.com/Forkk/MultiMC5"><span style=" text-decoration: underline; color:#0000ff;">http://github.com/Forkk/MultiMC5</span></a></p></body></html></string> + <string><html><head/><body><p><a href="http://github.com/Forkk/MultiMC5"><span style=" text-decoration: underline; color:#0000ff;">http://github.com/MultiMC/MultiMC5</span></a></p></body></html></string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -160,7 +160,7 @@ <x>0</x> <y>0</y> <width>432</width> - <height>153</height> + <height>191</height> </rect> </property> <attribute name="label"> @@ -176,16 +176,37 @@ <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Andrew Okin &lt;<a href="mailto:forkk@forkk.net"><span style=" text-decoration: underline; color:#0000ff;">forkk@forkk.net</span></a>&gt;</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Petr Mrázek &lt;<a href="mailto:peterix@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">peterix@gmail.com</span></a>&gt;</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Orochimarufan &lt;<a href="mailto:orochimarufan.x3@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">orochimarufan.x3@gmail.com</span></a>&gt;</p></body></html></string> +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">Andrew Okin &lt;</span><a href="mailto:forkk@forkk.net"><span style=" font-family:'Ubuntu'; font-size:11pt; text-decoration: underline; color:#0000ff;">forkk@forkk.net</span></a><span style=" font-family:'Ubuntu'; font-size:11pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">Petr Mrázek &lt;</span><a href="mailto:peterix@gmail.com"><span style=" font-family:'Ubuntu'; font-size:11pt; text-decoration: underline; color:#0000ff;">peterix@gmail.com</span></a><span style=" font-family:'Ubuntu'; font-size:11pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">Orochimarufan &lt;</span><a href="mailto:orochimarufan.x3@gmail.com"><span style=" font-family:'Ubuntu'; font-size:11pt; text-decoration: underline; color:#0000ff;">orochimarufan.x3@gmail.com</span></a><span style=" font-family:'Ubuntu'; font-size:11pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">TakSuyu &lt;</span><a href="mailto:taksuyu@gmail.com"><span style=" font-family:'Ubuntu'; font-size:11pt; text-decoration: underline; color:#0000ff;">taksuyu@gmail.com</span></a><span style=" font-family:'Ubuntu'; font-size:11pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">Sky Welch &lt;</span><span style=" font-family:'Ubuntu'; font-size:11pt; text-decoration: underline; color:#0000ff;">multimc@bunnies.cc</span><span style=" font-family:'Ubuntu'; font-size:11pt;">&gt;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">Kilobyte &lt;</span><a href="mailto:stiepen22@gmx.de"><span style=" font-family:'Ubuntu'; font-size:11pt; text-decoration: underline; color:#0000ff;">stiepen22@gmx.de</span></a><span style=" font-family:'Ubuntu'; font-size:11pt;">&gt;</span></p></body></html></string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="translationInfo"> + <property name="text"> + <string extracomment="Hey, Translator, feel free to put credit to you here">No Language file loaded.</string> + </property> + <property name="readOnly"> + <bool>true</bool> </property> </widget> </item> </layout> </widget> <widget class="QWidget" name="licensePage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>432</width> + <height>191</height> + </rect> + </property> <attribute name="label"> <string>License</string> </attribute> @@ -205,44 +226,45 @@ p, li { white-space: pre-wrap; } <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2012 MultiMC Contributors</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">you may not use this file except in compliance with the License.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">You may obtain a copy of the License at</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> http://www.apache.org/licenses/LICENSE-2.0</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Unless required by applicable law or agreed to in writing, software</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">See the License for the specific language governing permissions and</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">limitations under the License.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">MultiMC uses bspatch, </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2003-2005 Colin Percival</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">All rights reserved</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Redistribution and use in source and binary forms, with or without</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">modification, are permitted providing that the following conditions</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">are met: </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">1. Redistributions of source code must retain the above copyright</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> notice, this list of conditions and the following disclaimer.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">2. Redistributions in binary form must reproduce the above copyright</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> notice, this list of conditions and the following disclaimer in the</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> documentation and/or other materials provided with the distribution.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">POSSIBILITY OF SUCH DAMAGE.</span></p></body></html></string> +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">Copyright 2012 MultiMC Contributors</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">you may not use this file except in compliance with the License.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">You may obtain a copy of the License at</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;"> http://www.apache.org/licenses/LICENSE-2.0</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">Unless required by applicable law or agreed to in writing, software</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">See the License for the specific language governing permissions and</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">limitations under the License.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">MultiMC uses QSLog, </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">Copyright (c) 2010, Razvan Petru</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">All rights reserved.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">Redistribution and use in source and binary forms, with or without modification,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">are permitted provided that the following conditions are met:</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">* Redistributions of source code must retain the above copyright notice, this</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;"> list of conditions and the following disclaimer.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">* Redistributions in binary form must reproduce the above copyright notice, this</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;"> list of conditions and the following disclaimer in the documentation and/or other</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;"> materials provided with the distribution.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">* The name of the contributors may not be used to endorse or promote products</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;"> derived from this software without specific prior written permission.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot; AND</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">OF THE POSSIBILITY OF SUCH DAMAGE.</span></p></body></html></string> </property> </widget> </item> @@ -254,6 +276,9 @@ p, li { white-space: pre-wrap; } <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QPushButton" name="aboutQt"> + <property name="autoFillBackground"> + <bool>false</bool> + </property> <property name="text"> <string>About Qt</string> </property> @@ -283,6 +308,8 @@ p, li { white-space: pre-wrap; } </item> </layout> </widget> - <resources/> + <resources> + <include location="../multimc.qrc"/> + </resources> <connections/> </ui> diff --git a/gui/consolewindow.cpp b/gui/consolewindow.cpp index 811900a2..deeedd65 100644 --- a/gui/consolewindow.cpp +++ b/gui/consolewindow.cpp @@ -2,13 +2,19 @@ #include "ui_consolewindow.h" #include <QScrollBar> +#include <QMessageBox> -ConsoleWindow::ConsoleWindow(QWidget *parent) : +#include <gui/platform.h> + +ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent) : QDialog(parent), ui(new Ui::ConsoleWindow), - m_mayclose(true) + m_mayclose(true), + proc(mcproc) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); + connect(mcproc, SIGNAL(ended()), this, SLOT(onEnded())); } ConsoleWindow::~ConsoleWindow() @@ -20,7 +26,7 @@ void ConsoleWindow::writeColor(QString text, const char *color) { // append a paragraph if (color != nullptr) - ui->text->appendHtml(QString("<font color=%1>%2</font>").arg(color).arg(text)); + ui->text->appendHtml(QString("<font color=\"%1\">%2</font>").arg(color).arg(text)); else ui->text->appendPlainText(text); // scroll down @@ -33,6 +39,11 @@ void ConsoleWindow::write(QString data, MessageLevel::Enum mode) if (data.endsWith('\n')) data = data.left(data.length()-1); QStringList paragraphs = data.split('\n'); + for(QString ¶graph : paragraphs) + { + paragraph = paragraph.trimmed(); + } + QListIterator<QString> iter(paragraphs); if (mode == MessageLevel::MultiMC) while(iter.hasNext()) @@ -40,6 +51,15 @@ void ConsoleWindow::write(QString data, MessageLevel::Enum mode) else if (mode == MessageLevel::Error) while(iter.hasNext()) writeColor(iter.next(), "red"); + else if (mode == MessageLevel::Warning) + while(iter.hasNext()) + writeColor(iter.next(), "orange"); + else if (mode == MessageLevel::Fatal) + while(iter.hasNext()) + writeColor(iter.next(), "pink"); + else if (mode == MessageLevel::Debug) + while(iter.hasNext()) + writeColor(iter.next(), "green"); // TODO: implement other MessageLevels else while(iter.hasNext()) @@ -72,3 +92,26 @@ void ConsoleWindow::closeEvent(QCloseEvent * event) else QDialog::closeEvent(event); } + +void ConsoleWindow::on_btnKillMinecraft_clicked() +{ + ui->btnKillMinecraft->setEnabled(false); + QMessageBox r_u_sure; + //: Main question of the kill confirmation dialog + r_u_sure.setText(tr("Kill Minecraft?")); + r_u_sure.setInformativeText(tr("This can cause the instance to get corrupted and should only be used if Minecraft is frozen for some reason")); + r_u_sure.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + r_u_sure.setDefaultButton(QMessageBox::Yes); + if (r_u_sure.exec() == QMessageBox::Yes) + proc->killMinecraft(); + else + ui->btnKillMinecraft->setEnabled(true); + r_u_sure.close(); +} + +void ConsoleWindow::onEnded() +{ + ui->btnKillMinecraft->setEnabled(false); + // TODO: Check why this doesn't work + if (!proc->exitCode()) this->close(); +} diff --git a/gui/consolewindow.h b/gui/consolewindow.h index 60bec69f..6a6c9e50 100644 --- a/gui/consolewindow.h +++ b/gui/consolewindow.h @@ -13,7 +13,7 @@ class ConsoleWindow : public QDialog Q_OBJECT public: - explicit ConsoleWindow(QWidget *parent = 0); + explicit ConsoleWindow(MinecraftProcess *proc, QWidget *parent = 0); ~ConsoleWindow(); /** @@ -48,12 +48,15 @@ public slots: private slots: void on_closeButton_clicked(); + void on_btnKillMinecraft_clicked(); + void onEnded(); protected: void closeEvent(QCloseEvent *); private: Ui::ConsoleWindow *ui; + MinecraftProcess *proc; bool m_mayclose; }; diff --git a/gui/consolewindow.ui b/gui/consolewindow.ui index 9a766543..8dc80015 100644 --- a/gui/consolewindow.ui +++ b/gui/consolewindow.ui @@ -63,6 +63,13 @@ </spacer> </item> <item> + <widget class="QPushButton" name="btnKillMinecraft"> + <property name="text"> + <string>Kill Minecraft</string> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="closeButton"> <property name="text"> <string>Close</string> diff --git a/gui/instancedelegate.h b/gui/instancedelegate.h index c80f95a5..56bc34ba 100644 --- a/gui/instancedelegate.h +++ b/gui/instancedelegate.h @@ -9,4 +9,4 @@ public: protected: void paint ( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const; -};
\ No newline at end of file +}; diff --git a/gui/instancesettings.cpp b/gui/instancesettings.cpp index dfb04f3d..73eb6627 100644 --- a/gui/instancesettings.cpp +++ b/gui/instancesettings.cpp @@ -19,12 +19,14 @@ #include "instancesettings.h" #include "ui_instancesettings.h" +#include "gui/platform.h" InstanceSettings::InstanceSettings( SettingsObject * obj, QWidget *parent) : m_obj(obj), QDialog(parent), ui(new Ui::InstanceSettings) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); loadSettings(); } diff --git a/gui/instancesettings.ui b/gui/instancesettings.ui index 49309f11..b536e9ff 100644 --- a/gui/instancesettings.ui +++ b/gui/instancesettings.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>477</width> + <width>526</width> <height>590</height> </rect> </property> @@ -249,7 +249,7 @@ <number>64</number> </property> <property name="maximum"> - <number>512</number> + <number>999999999</number> </property> <property name="singleStep"> <number>8</number> @@ -395,6 +395,7 @@ </widget> <tabstops> <tabstop>settingsTabs</tabstop> + <tabstop>buttonBox</tabstop> <tabstop>windowSizeGroupBox</tabstop> <tabstop>maximizedCheckBox</tabstop> <tabstop>windowWidthSpinBox</tabstop> @@ -407,6 +408,7 @@ <tabstop>memoryGroupBox</tabstop> <tabstop>minMemSpinBox</tabstop> <tabstop>maxMemSpinBox</tabstop> + <tabstop>permGenSpinBox</tabstop> <tabstop>javaSettingsGroupBox</tabstop> <tabstop>javaPathTextBox</tabstop> <tabstop>pushButton</tabstop> @@ -414,7 +416,6 @@ <tabstop>customCommandsGroupBox</tabstop> <tabstop>preLaunchCmdTextBox</tabstop> <tabstop>postExitCmdTextBox</tabstop> - <tabstop>buttonBox</tabstop> </tabstops> <resources/> <connections/> diff --git a/gui/logindialog.cpp b/gui/logindialog.cpp index a4dad1c1..aeaaaa9e 100644 --- a/gui/logindialog.cpp +++ b/gui/logindialog.cpp @@ -16,16 +16,25 @@ #include "logindialog.h" #include "ui_logindialog.h" #include "keyring.h" -#include <QDebug> +#include "gui/platform.h" +#include "MultiMC.h" + +#include <QFile> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonParseError> +#include "logic/net/HttpMetaCache.h" +#include <logger/QsLog.h> LoginDialog::LoginDialog(QWidget *parent, const QString& loginErrMsg) : QDialog(parent), ui(new Ui::LoginDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); - //TODO: make translateable - offlineButton = new QPushButton("Offline Once"); + //: Use offline mode one time + offlineButton = new QPushButton(tr("Offline Once")); ui->loginButtonBox->addButton(offlineButton, QDialogButtonBox::ActionRole); @@ -33,8 +42,8 @@ LoginDialog::LoginDialog(QWidget *parent, const QString& loginErrMsg) : isOnline_ = true; onlineForced = false; - //FIXME: translateable? - ui->usernameTextBox->lineEdit()->setPlaceholderText(QApplication::translate("LoginDialog", "Name", 0)); + //: The username during login (placeholder) + ui->usernameTextBox->lineEdit()->setPlaceholderText(tr("Name")); connect(ui->usernameTextBox, SIGNAL(currentTextChanged(QString)), this, SLOT(userTextChanged(QString))); connect(ui->forgetButton, SIGNAL(clicked(bool)), this, SLOT(forgetCurrentUser())); @@ -49,6 +58,8 @@ LoginDialog::LoginDialog(QWidget *parent, const QString& loginErrMsg) : arg(loginErrMsg)); } + ui->lblFace->setVisible(false); + resize(minimumSizeHint()); layout()->setSizeConstraint(QLayout::SetFixedSize); Keyring * k = Keyring::instance(); @@ -109,7 +120,7 @@ void LoginDialog::passwordToggled ( bool state ) blockToggles = true; if(!state) { - qDebug() << "password disabled"; + QLOG_DEBUG() << "password disabled"; } else { @@ -117,7 +128,7 @@ void LoginDialog::passwordToggled ( bool state ) { ui->rememberUsernameCheckbox->setChecked(true); } - qDebug() << "password enabled"; + QLOG_DEBUG() << "password enabled"; } blockToggles = false; } @@ -134,11 +145,11 @@ void LoginDialog::usernameToggled ( bool state ) { ui->rememberPasswordCheckbox->setChecked(false); } - qDebug() << "username disabled"; + QLOG_DEBUG() << "username disabled"; } else { - qDebug() << "username enabled"; + QLOG_DEBUG() << "username enabled"; } blockToggles = false; } @@ -149,13 +160,53 @@ void LoginDialog::userTextChanged ( const QString& user ) blockToggles = true; Keyring * k = Keyring::instance(); QStringList sl = k->getStoredAccounts("minecraft"); + bool gotFace = false; + if(sl.contains(user)) { ui->rememberUsernameCheckbox->setChecked(true); QString passwd = k->getPassword("minecraft",user); ui->rememberPasswordCheckbox->setChecked(!passwd.isEmpty()); ui->passwordTextBox->setText(passwd); + + QByteArray data; + { + auto filename = MMC->metacache()->resolveEntry("skins", "skins.json")->getFullPath(); + QFile listFile(filename); + if(!listFile.open(QIODevice::ReadOnly)) + return; + data = listFile.readAll(); + } + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + QJsonObject root = jsonDoc.object(); + QJsonObject mappings = root.value("mappings").toObject(); + + if(!mappings[user].isUndefined()) + { + QJsonArray usernames = mappings.value(user).toArray(); + if(!usernames.isEmpty()) + { + QString mapped_username = usernames[0].toString(); + + if(!mapped_username.isEmpty()) + { + QFile fskin(MMC->metacache()->resolveEntry("skins", mapped_username + ".png")->getFullPath()); + if(fskin.exists()) + { + QPixmap skin(MMC->metacache()->resolveEntry("skins", mapped_username + ".png")->getFullPath()); + QPixmap face = skin.copy(8, 8, 8, 8).scaled(48, 48, Qt::KeepAspectRatio); + + ui->lblFace->setPixmap(face); + gotFace = true; + } + } + } + } } + + ui->lblFace->setVisible(gotFace); blockToggles = false; } @@ -194,4 +245,4 @@ void LoginDialog::forceOnline() { onlineForced = true; offlineButton->setEnabled(false); -}
\ No newline at end of file +} diff --git a/gui/logindialog.ui b/gui/logindialog.ui index 0aaad52b..46965425 100644 --- a/gui/logindialog.ui +++ b/gui/logindialog.ui @@ -23,10 +23,34 @@ </item> <item> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="usernameLabel"> + <item row="0" column="4" rowspan="2"> + <widget class="QLabel" name="lblFace"> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="sizeIncrement"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> <property name="text"> - <string>Username:</string> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../multimc.qrc">:/icons/instances/steve</pixmap> + </property> + <property name="scaledContents"> + <bool>true</bool> </property> </widget> </item> @@ -37,6 +61,13 @@ </property> </widget> </item> + <item row="0" column="0"> + <widget class="QLabel" name="usernameLabel"> + <property name="text"> + <string>Username:</string> + </property> + </widget> + </item> <item row="1" column="0"> <widget class="QLabel" name="passwordLabel"> <property name="text"> @@ -54,7 +85,7 @@ </property> </widget> </item> - <item row="0" column="2" rowspan="2"> + <item row="0" column="5" rowspan="2"> <widget class="QPushButton" name="forgetButton"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> @@ -111,7 +142,9 @@ </item> </layout> </widget> - <resources/> + <resources> + <include location="../multimc.qrc"/> + </resources> <connections> <connection> <sender>loginButtonBox</sender> diff --git a/gui/lwjglselectdialog.cpp b/gui/lwjglselectdialog.cpp index 9de92754..09963ce0 100644 --- a/gui/lwjglselectdialog.cpp +++ b/gui/lwjglselectdialog.cpp @@ -13,8 +13,10 @@ * limitations under the License. */ +#include "MultiMC.h" #include "lwjglselectdialog.h" #include "ui_lwjglselectdialog.h" +#include "gui/platform.h" #include "logic/lists/LwjglVersionList.h" @@ -22,13 +24,15 @@ LWJGLSelectDialog::LWJGLSelectDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LWJGLSelectDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); ui->labelStatus->setVisible(false); - ui->lwjglListView->setModel(&LWJGLVersionList::get()); + auto lwjgllist = MMC->lwjgllist(); + ui->lwjglListView->setModel(lwjgllist.get()); - connect(&LWJGLVersionList::get(), SIGNAL(loadingStateUpdated(bool)), SLOT(loadingStateUpdated(bool))); - connect(&LWJGLVersionList::get(), SIGNAL(loadListFailed(QString)), SLOT(loadingFailed(QString))); - loadingStateUpdated(LWJGLVersionList::get().isLoading()); + connect(lwjgllist.get(), SIGNAL(loadingStateUpdated(bool)), SLOT(loadingStateUpdated(bool))); + connect(lwjgllist.get(), SIGNAL(loadListFailed(QString)), SLOT(loadingFailed(QString))); + loadingStateUpdated(lwjgllist->isLoading()); } LWJGLSelectDialog::~LWJGLSelectDialog() @@ -38,15 +42,15 @@ LWJGLSelectDialog::~LWJGLSelectDialog() QString LWJGLSelectDialog::selectedVersion() const { - return LWJGLVersionList::get().data( + return MMC->lwjgllist()->data( ui->lwjglListView->selectionModel()->currentIndex(), Qt::DisplayRole).toString(); } void LWJGLSelectDialog::on_refreshButton_clicked() { - if (!LWJGLVersionList::get().isLoading()) - LWJGLVersionList::get().loadList(); + if (!MMC->lwjgllist()->isLoading()) + MMC->lwjgllist()->loadList(); } void LWJGLSelectDialog::loadingStateUpdated(bool loading) @@ -54,7 +58,7 @@ void LWJGLSelectDialog::loadingStateUpdated(bool loading) setEnabled(!loading); if (loading) { - ui->labelStatus->setText("Loading LWJGL version list..."); + ui->labelStatus->setText(tr("Loading LWJGL version list...")); ui->labelStatus->setStyleSheet("QLabel { color: black; }"); } ui->labelStatus->setVisible(loading); diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 82483bf2..ab718f98 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -42,24 +42,28 @@ #include "gui/settingsdialog.h" #include "gui/newinstancedialog.h" #include "gui/logindialog.h" -#include "gui/taskdialog.h" +#include "gui/ProgressDialog.h" #include "gui/aboutdialog.h" #include "gui/versionselectdialog.h" #include "gui/lwjglselectdialog.h" #include "gui/consolewindow.h" #include "gui/instancesettings.h" +#include "gui/platform.h" #include "logic/lists/InstanceList.h" #include "logic/lists/MinecraftVersionList.h" #include "logic/lists/LwjglVersionList.h" #include "logic/lists/IconList.h" +#include "logic/lists/JavaVersionList.h" + +#include "logic/net/LoginTask.h" -#include "logic/tasks/LoginTask.h" #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logic/MinecraftProcess.h" #include "logic/OneSixAssets.h" #include "logic/OneSixUpdate.h" +#include "logic/JavaUtils.h" #include "logic/LegacyInstance.h" @@ -68,20 +72,20 @@ #include "LabeledToolButton.h" #include "EditNotesDialog.h" -MainWindow::MainWindow ( QWidget *parent ) - :QMainWindow ( parent ), ui ( new Ui::MainWindow ) +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { - ui->setupUi ( this ); - setWindowTitle ( QString ( "MultiMC %1" ).arg ( MMC->version().toString() ) ); - + MultiMCPlatform::fixWM_CLASS(this); + ui->setupUi(this); + setWindowTitle(QString("MultiMC %1").arg(MMC->version().toString())); + // Set the selected instance to null m_selectedInstance = nullptr; // Set active instance to null. m_activeInst = nullptr; - + // OSX magic. setUnifiedTitleAndToolBarOnMac(true); - + // The instance action toolbar customizations { ui->instanceToolBar->setEnabled(false); @@ -92,44 +96,45 @@ MainWindow::MainWindow ( QWidget *parent ) connect(renameButton, SIGNAL(clicked(bool)), SLOT(on_actionRenameInstance_triggered())); ui->instanceToolBar->insertWidget(ui->actionLaunchInstance, renameButton); ui->instanceToolBar->insertSeparator(ui->actionLaunchInstance); - renameButton->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); + renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } - + // Create the instance list widget { - view = new KCategorizedView ( ui->centralWidget ); - drawer = new KCategoryDrawer ( view ); - - view->setSelectionMode ( QAbstractItemView::SingleSelection ); - view->setCategoryDrawer ( drawer ); - view->setCollapsibleBlocks ( true ); - view->setViewMode ( QListView::IconMode ); - view->setFlow ( QListView::LeftToRight ); + view = new KCategorizedView(ui->centralWidget); + drawer = new KCategoryDrawer(view); + + view->setSelectionMode(QAbstractItemView::SingleSelection); + view->setCategoryDrawer(drawer); + view->setCollapsibleBlocks(true); + view->setViewMode(QListView::IconMode); + view->setFlow(QListView::LeftToRight); view->setWordWrap(true); - view->setMouseTracking ( true ); - view->viewport()->setAttribute ( Qt::WA_Hover ); + view->setMouseTracking(true); + view->viewport()->setAttribute(Qt::WA_Hover); auto delegate = new ListViewDelegate(); view->setItemDelegate(delegate); view->setSpacing(10); view->setUniformItemWidths(true); - + // do not show ugly blue border on the mac view->setAttribute(Qt::WA_MacShowFocusRect, false); - + view->installEventFilter(this); - proxymodel = new InstanceProxyModel ( this ); - proxymodel->setSortRole ( KCategorizedSortFilterProxyModel::CategorySortRole ); - proxymodel->setFilterRole ( KCategorizedSortFilterProxyModel::CategorySortRole ); - //proxymodel->setDynamicSortFilter ( true ); - - // FIXME: instList should be global-ish, or at least not tied to the main window... maybe the application itself? - proxymodel->setSourceModel ( MMC->instances() ); - proxymodel->sort ( 0 ); - view->setFrameShape ( QFrame::NoFrame ); - view->setModel ( proxymodel ); - - ui->horizontalLayout->addWidget ( view ); + proxymodel = new InstanceProxyModel(this); + proxymodel->setSortRole(KCategorizedSortFilterProxyModel::CategorySortRole); + proxymodel->setFilterRole(KCategorizedSortFilterProxyModel::CategorySortRole); + // proxymodel->setDynamicSortFilter ( true ); + + // FIXME: instList should be global-ish, or at least not tied to the main window... + // maybe the application itself? + proxymodel->setSourceModel(MMC->instances().get()); + proxymodel->sort(0); + view->setFrameShape(QFrame::NoFrame); + view->setModel(proxymodel); + + ui->horizontalLayout->addWidget(view); } // The cat background { @@ -139,29 +144,27 @@ MainWindow::MainWindow ( QWidget *parent ) setCatBackground(cat_enable); } // start instance when double-clicked - connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(instanceActivated(const QModelIndex &))); + connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, + SLOT(instanceActivated(const QModelIndex &))); // track the selection -- update the instance toolbar - connect( - view->selectionModel(), - SIGNAL(currentChanged(const QModelIndex &,const QModelIndex &)), - this, - SLOT(instanceChanged(const QModelIndex &,const QModelIndex &)) - ); + connect(view->selectionModel(), + SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, + SLOT(instanceChanged(const QModelIndex &, const QModelIndex &))); // model reset -> selection is invalid. All the instance pointers are wrong. // FIXME: stop using POINTERS everywhere - connect(MMC->instances() ,SIGNAL(dataIsInvalid()),SLOT(selectionBad())); - + connect(MMC->instances().get(), SIGNAL(dataIsInvalid()), SLOT(selectionBad())); + // run the things that load and download other things... FIXME: this is NOT the place // FIXME: invisible actions in the background = NOPE. { - if (!MinecraftVersionList::getMainList().isLoaded()) + if (!MMC->minecraftlist()->isLoaded()) { - m_versionLoadTask = MinecraftVersionList::getMainList().getLoadTask(); + m_versionLoadTask = MMC->minecraftlist()->getLoadTask(); startTask(m_versionLoadTask); } - if (!LWJGLVersionList::get().isLoaded()) + if (!MMC->lwjgllist()->isLoaded()) { - LWJGLVersionList::get().loadList(); + MMC->lwjgllist()->loadList(); } assets_downloader = new OneSixAssets(); assets_downloader->start(); @@ -176,57 +179,55 @@ MainWindow::~MainWindow() delete assets_downloader; } -bool MainWindow::eventFilter ( QObject* obj, QEvent* ev ) +bool MainWindow::eventFilter(QObject *obj, QEvent *ev) { - if(obj == view) + if (obj == view) { if (ev->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev); - switch(keyEvent->key()) + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); + switch (keyEvent->key()) { - case Qt::Key_Enter: - case Qt::Key_Return: - on_actionLaunchInstance_triggered(); - return true; - case Qt::Key_Delete: - on_actionDeleteInstance_triggered(); - return true; - case Qt::Key_F5: - on_actionRefresh_triggered(); - return true; - case Qt::Key_F2: - on_actionRenameInstance_triggered(); - return true; - default: - break; + case Qt::Key_Enter: + case Qt::Key_Return: + on_actionLaunchInstance_triggered(); + return true; + case Qt::Key_Delete: + on_actionDeleteInstance_triggered(); + return true; + case Qt::Key_F5: + on_actionRefresh_triggered(); + return true; + case Qt::Key_F2: + on_actionRenameInstance_triggered(); + return true; + default: + break; } } } - return QMainWindow::eventFilter ( obj, ev ); + return QMainWindow::eventFilter(obj, ev); } -void MainWindow::onCatToggled ( bool state ) +void MainWindow::onCatToggled(bool state) { setCatBackground(state); MMC->settings()->set("TheCat", state); } -void MainWindow::setCatBackground ( bool enabled ) +void MainWindow::setCatBackground(bool enabled) { - if(enabled) + if (enabled) { - view->setStyleSheet( - "QListView" - "{" - "background-image: url(:/backgrounds/kitteh);" - "background-attachment: fixed;" - "background-clip: padding;" - "background-position: top right;" - "background-repeat: none;" - "background-color:palette(base);" - "}" - ); + view->setStyleSheet("QListView" + "{" + "background-image: url(:/backgrounds/kitteh);" + "background-attachment: fixed;" + "background-clip: padding;" + "background-position: top right;" + "background-repeat: none;" + "background-color:palette(base);" + "}"); } else { @@ -234,37 +235,37 @@ void MainWindow::setCatBackground ( bool enabled ) } } - -void MainWindow::instanceActivated ( QModelIndex index ) +void MainWindow::instanceActivated(QModelIndex index) { - if(!index.isValid()) + if (!index.isValid()) return; - BaseInstance * inst = (BaseInstance *) index.data(InstanceList::InstancePointerRole).value<void *>(); + BaseInstance *inst = + (BaseInstance *)index.data(InstanceList::InstancePointerRole).value<void *>(); doLogin(); } void MainWindow::on_actionAddInstance_triggered() { - if (!MinecraftVersionList::getMainList().isLoaded() && - m_versionLoadTask && m_versionLoadTask->isRunning()) + if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask && + m_versionLoadTask->isRunning()) { QEventLoop waitLoop; waitLoop.connect(m_versionLoadTask, SIGNAL(failed(QString)), SLOT(quit())); waitLoop.connect(m_versionLoadTask, SIGNAL(succeeded()), SLOT(quit())); waitLoop.exec(); } - - NewInstanceDialog newInstDlg( this ); + + NewInstanceDialog newInstDlg(this); if (!newInstDlg.exec()) return; - + BaseInstance *newInstance = NULL; - + QString instDirName = DirNameFromString(newInstDlg.instName()); QString instDir = PathCombine(MMC->settings()->get("InstanceDir").toString(), instDirName); - + auto &loader = InstanceFactory::get(); - + auto error = loader.createInstance(newInstance, newInstDlg.selectedVersion(), instDir); QString errorMsg = QString("Failed to create instance %1: ").arg(instDirName); switch (error) @@ -274,17 +275,17 @@ void MainWindow::on_actionAddInstance_triggered() newInstance->setIconKey(newInstDlg.iconKey()); MMC->instances()->add(InstancePtr(newInstance)); return; - + case InstanceFactory::InstExists: errorMsg += "An instance with the given directory name already exists."; QMessageBox::warning(this, "Error", errorMsg); break; - + case InstanceFactory::CantCreateDir: errorMsg += "Failed to create the instance directory."; QMessageBox::warning(this, "Error", errorMsg); break; - + default: errorMsg += QString("Unknown instance loader error %1").arg(error); QMessageBox::warning(this, "Error", errorMsg); @@ -294,12 +295,12 @@ void MainWindow::on_actionAddInstance_triggered() void MainWindow::on_actionChangeInstIcon_triggered() { - if(!m_selectedInstance) + if (!m_selectedInstance) return; - + IconPickerDialog dlg(this); dlg.exec(m_selectedInstance->iconKey()); - if(dlg.result() == QDialog::Accepted) + if (dlg.result() == QDialog::Accepted) { m_selectedInstance->setIconKey(dlg.selectedIconKey); auto ico = MMC->icons()->getIcon(dlg.selectedIconKey); @@ -307,25 +308,23 @@ void MainWindow::on_actionChangeInstIcon_triggered() } } - void MainWindow::on_actionChangeInstGroup_triggered() { - if(!m_selectedInstance) + if (!m_selectedInstance) return; - + bool ok = false; - QString name ( m_selectedInstance->group() ); - name = QInputDialog::getText ( this, tr ( "Group name" ), tr ( "Enter a new group name." ), - QLineEdit::Normal, name, &ok ); - if(ok) + QString name(m_selectedInstance->group()); + name = QInputDialog::getText(this, tr("Group name"), tr("Enter a new group name."), + QLineEdit::Normal, name, &ok); + if (ok) m_selectedInstance->setGroupPost(name); } - void MainWindow::on_actionViewInstanceFolder_triggered() { - QString str = MMC->settings()->get ( "InstanceDir" ).toString(); - openDirInDefaultProgram ( str ); + QString str = MMC->settings()->get("InstanceDir").toString(); + openDirInDefaultProgram(str); } void MainWindow::on_actionRefresh_triggered() @@ -335,59 +334,58 @@ void MainWindow::on_actionRefresh_triggered() void MainWindow::on_actionViewCentralModsFolder_triggered() { - openDirInDefaultProgram ( MMC->settings()->get ( "CentralModsDir" ).toString() , true); + openDirInDefaultProgram(MMC->settings()->get("CentralModsDir").toString(), true); } void MainWindow::on_actionConfig_Folder_triggered() { - if(m_selectedInstance) + if (m_selectedInstance) { QString str = m_selectedInstance->instanceConfigFolder(); - openDirInDefaultProgram ( QDir(str).absolutePath() ); + openDirInDefaultProgram(QDir(str).absolutePath()); } } - void MainWindow::on_actionCheckUpdate_triggered() { - } void MainWindow::on_actionSettings_triggered() { - SettingsDialog dialog ( this ); + SettingsDialog dialog(this); dialog.exec(); } void MainWindow::on_actionReportBug_triggered() { - openWebPage ( QUrl ( "http://jira.forkk.net/browse/MMC" ) ); + openWebPage(QUrl("http://multimc.myjetbrains.com/youtrack/dashboard#newissue=yes")); } void MainWindow::on_actionNews_triggered() { - openWebPage ( QUrl ( "http://forkk.net/tag/multimc.html" ) ); + openWebPage(QUrl("http://multimc.org/posts.html")); } void MainWindow::on_actionAbout_triggered() { - AboutDialog dialog ( this ); + AboutDialog dialog(this); dialog.exec(); } -void MainWindow::on_mainToolBar_visibilityChanged ( bool ) +void MainWindow::on_mainToolBar_visibilityChanged(bool) { // Don't allow hiding the main toolbar. // This is the only way I could find to prevent it... :/ - ui->mainToolBar->setVisible ( true ); + ui->mainToolBar->setVisible(true); } void MainWindow::on_actionDeleteInstance_triggered() { if (m_selectedInstance) { - int response = QMessageBox::question(this, "CAREFUL", - QString("This is permanent! Are you sure?\nAbout to delete: ") + m_selectedInstance->name()); + int response = QMessageBox::question( + this, "CAREFUL", QString("This is permanent! Are you sure?\nAbout to delete: ") + + m_selectedInstance->name()); if (response == QMessageBox::Yes) { m_selectedInstance->nuke(); @@ -397,31 +395,31 @@ void MainWindow::on_actionDeleteInstance_triggered() void MainWindow::on_actionRenameInstance_triggered() { - if(m_selectedInstance) + if (m_selectedInstance) { bool ok = false; - QString name ( m_selectedInstance->name() ); - name = QInputDialog::getText ( this, tr ( "Instance name" ), tr ( "Enter a new instance name." ), - QLineEdit::Normal, name, &ok ); - + QString name(m_selectedInstance->name()); + name = + QInputDialog::getText(this, tr("Instance name"), tr("Enter a new instance name."), + QLineEdit::Normal, name, &ok); + if (name.length() > 0) { - if(ok && name.length()) + if (ok && name.length()) { m_selectedInstance->setName(name); renameButton->setText(name); } } - } } void MainWindow::on_actionViewSelectedInstFolder_triggered() { - if(m_selectedInstance) + if (m_selectedInstance) { QString str = m_selectedInstance->instanceRoot(); - openDirInDefaultProgram ( QDir(str).absolutePath() ); + openDirInDefaultProgram(QDir(str).absolutePath()); } } @@ -430,65 +428,70 @@ void MainWindow::on_actionEditInstMods_triggered() if (m_selectedInstance) { auto dialog = m_selectedInstance->createModEditDialog(this); - if(dialog) + if (dialog) dialog->exec(); dialog->deleteLater(); } } -void MainWindow::closeEvent ( QCloseEvent *event ) +void MainWindow::closeEvent(QCloseEvent *event) { // Save the window state and geometry. // TODO: Make this work with the new settings system. -// settings->getConfig().setValue("MainWindowGeometry", saveGeometry()); -// settings->getConfig().setValue("MainWindowState", saveState()); - QMainWindow::closeEvent ( event ); + // settings->getConfig().setValue("MainWindowGeometry", saveGeometry()); + // settings->getConfig().setValue("MainWindowState", saveState()); + QMainWindow::closeEvent(event); } - -void MainWindow::on_instanceView_customContextMenuRequested ( const QPoint &pos ) +/* +void MainWindow::on_instanceView_customContextMenuRequested(const QPoint &pos) { - QMenu *instContextMenu = new QMenu ( "Instance", this ); + QMenu *instContextMenu = new QMenu("Instance", this); // Add the actions from the toolbar to the context menu. - instContextMenu->addActions ( ui->instanceToolBar->actions() ); + instContextMenu->addActions(ui->instanceToolBar->actions()); - instContextMenu->exec ( view->mapToGlobal ( pos ) ); + instContextMenu->exec(view->mapToGlobal(pos)); } - +*/ void MainWindow::on_actionLaunchInstance_triggered() { - if(m_selectedInstance) + if (m_selectedInstance) { doLogin(); } } -void MainWindow::doLogin(const QString& errorMsg) +void MainWindow::doLogin(const QString &errorMsg) { if (!m_selectedInstance) return; - - LoginDialog* loginDlg = new LoginDialog(this, errorMsg); + + LoginDialog *loginDlg = new LoginDialog(this, errorMsg); if (!m_selectedInstance->lastLaunch()) loginDlg->forceOnline(); - + loginDlg->exec(); - if(loginDlg->result() == QDialog::Accepted) + if (loginDlg->result() == QDialog::Accepted) { - if (loginDlg->isOnline()) + if (loginDlg->isOnline()) { UserInfo uInfo{loginDlg->getUsername(), loginDlg->getPassword()}; - TaskDialog* tDialog = new TaskDialog(this); - LoginTask* loginTask = new LoginTask(uInfo, tDialog); - connect(loginTask, SIGNAL(succeeded()),SLOT(onLoginComplete()), Qt::QueuedConnection); - connect(loginTask, SIGNAL(failed(QString)), SLOT(doLogin(QString)), Qt::QueuedConnection); + ProgressDialog *tDialog = new ProgressDialog(this); + LoginTask *loginTask = new LoginTask(uInfo, tDialog); + connect(loginTask, SIGNAL(succeeded()), SLOT(onLoginComplete()), + Qt::QueuedConnection); + connect(loginTask, SIGNAL(failed(QString)), SLOT(doLogin(QString)), + Qt::QueuedConnection); m_activeInst = m_selectedInstance; tDialog->exec(loginTask); } else { - m_activeLogin = {loginDlg->getUsername(), QString("Offline"), qint64(-1)}; + QString user = loginDlg->getUsername(); + if (user.length() == 0) + user = QString("Player"); + m_activeLogin = {user, QString("Offline"), user, QString()}; m_activeInst = m_selectedInstance; launchInstance(m_activeInst, m_activeLogin); } @@ -497,22 +500,63 @@ void MainWindow::doLogin(const QString& errorMsg) void MainWindow::onLoginComplete() { - if(!m_activeInst) + if (!m_activeInst) return; - LoginTask * task = (LoginTask *) QObject::sender(); + LoginTask *task = (LoginTask *)QObject::sender(); m_activeLogin = task->getResult(); - + BaseUpdate *updateTask = m_activeInst->doUpdate(); - if(!updateTask) + if (!updateTask) { launchInstance(m_activeInst, m_activeLogin); } else { - TaskDialog *tDialog = new TaskDialog(this); - connect(updateTask, SIGNAL(succeeded()),SLOT(onGameUpdateComplete())); + ProgressDialog tDialog(this); + connect(updateTask, SIGNAL(succeeded()), SLOT(onGameUpdateComplete())); connect(updateTask, SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); - tDialog->exec(updateTask); + tDialog.exec(updateTask); + delete updateTask; + } + + auto job = new DownloadJob("Player skin: " + m_activeLogin.player_name); + + auto meta = MMC->metacache()->resolveEntry("skins", m_activeLogin.player_name + ".png"); + job->addCacheDownload(QUrl("http://skins.minecraft.net/MinecraftSkins/" + m_activeLogin.player_name + ".png"), meta); + meta->stale = true; + + job->start(); + auto filename = MMC->metacache()->resolveEntry("skins", "skins.json")->getFullPath(); + QFile listFile(filename); + + // Add skin mapping + QByteArray data; + { + if(!listFile.open(QIODevice::ReadWrite)) + { + QLOG_ERROR() << "Failed to open/make skins list JSON"; + return; + } + + data = listFile.readAll(); + } + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + QJsonObject root = jsonDoc.object(); + QJsonObject mappings = root.value("mappings").toObject(); + QJsonArray usernames = mappings.value(m_activeLogin.username).toArray(); + + if(!usernames.contains(m_activeLogin.player_name)) + { + usernames.prepend(m_activeLogin.player_name); + mappings[m_activeLogin.username] = usernames; + root["mappings"] = mappings; + jsonDoc.setObject(root); + + // QJson hack - shouldn't have to clear the file every time a save happens + listFile.resize(0); + listFile.write(jsonDoc.toJson()); } } @@ -529,15 +573,27 @@ void MainWindow::onGameUpdateError(QString error) void MainWindow::launchInstance(BaseInstance *instance, LoginResponse response) { Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL"); - - proc = instance->prepareForLaunch(response.username, response.sessionID); - if(!proc) + + proc = instance->prepareForLaunch(response); + if (!proc) return; - - console = new ConsoleWindow(); + + // Prepare GUI: If it shall stay open disable the required parts + if (MMC->settings()->get("NoHide").toBool()) + { + ui->actionLaunchInstance->setEnabled(false); + } + else + { + this->hide(); + } + + console = new ConsoleWindow(proc); console->show(); - connect(proc, SIGNAL(log(QString, MessageLevel::Enum)), - console, SLOT(write(QString, MessageLevel::Enum))); + connect(proc, SIGNAL(log(QString, MessageLevel::Enum)), console, + SLOT(write(QString, MessageLevel::Enum))); + connect(proc, SIGNAL(ended()), this, SLOT(instanceEnded())); + proc->setLogin(response.username, response.session_id); proc->launch(); } @@ -551,7 +607,7 @@ void MainWindow::taskEnd() QObject *sender = QObject::sender(); if (sender == m_versionLoadTask) m_versionLoadTask = NULL; - + sender->deleteLater(); } @@ -560,23 +616,27 @@ void MainWindow::startTask(Task *task) connect(task, SIGNAL(started()), SLOT(taskStart())); connect(task, SIGNAL(succeeded()), SLOT(taskEnd())); connect(task, SIGNAL(failed(QString)), SLOT(taskEnd())); - task->startTask(); + task->start(); } - // Create A Desktop Shortcut void MainWindow::on_actionMakeDesktopShortcut_triggered() { - QString name ( "Test" ); - name = QInputDialog::getText ( this, tr ( "MultiMC Shortcut" ), tr ( "Enter a Shortcut Name." ), QLineEdit::Normal, name ); + QString name("Test"); + name = QInputDialog::getText(this, tr("MultiMC Shortcut"), tr("Enter a Shortcut Name."), + QLineEdit::Normal, name); - Util::createShortCut ( Util::getDesktopDir(), QApplication::instance()->applicationFilePath(), QStringList() << "-dl" << QDir::currentPath() << "test", name, "application-x-octet-stream" ); + Util::createShortCut(Util::getDesktopDir(), QApplication::instance()->applicationFilePath(), + QStringList() << "-dl" << QDir::currentPath() << "test", name, + "application-x-octet-stream"); - QMessageBox::warning ( this, "Not useful", "A Dummy Shortcut was created. it will not do anything productive" ); + QMessageBox::warning( + this, tr("Not useful"), + tr("A Dummy Shortcut was created. it will not do anything productive")); } // BrowserDialog -void MainWindow::openWebPage ( QUrl url ) +void MainWindow::openWebPage(QUrl url) { QDesktopServices::openUrl(url); } @@ -585,11 +645,23 @@ void MainWindow::on_actionChangeInstMCVersion_triggered() { if (view->selectionModel()->selectedIndexes().count() < 1) return; - - VersionSelectDialog vselect(m_selectedInstance->versionList(), this); + + VersionSelectDialog vselect(m_selectedInstance->versionList().get(), + tr("Change Minecraft version"), this); + vselect.setFilter(1, "OneSix"); if (vselect.exec() && vselect.selectedVersion()) { - m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor); + if (m_selectedInstance->versionIsCustom()) + { + auto result = QMessageBox::warning( + this, tr("Are you sure?"), + tr("This will remove any library/version customization you did previously. " + "This includes things like Forge install and similar."), + QMessageBox::Ok, QMessageBox::Abort); + if (result != QMessageBox::Ok) + return; + } + m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor()); } } @@ -597,12 +669,12 @@ void MainWindow::on_actionChangeInstLWJGLVersion_triggered() { if (!m_selectedInstance) return; - + LWJGLSelectDialog lselect(this); lselect.exec(); if (lselect.result() == QDialog::Accepted) { - LegacyInstance * linst = (LegacyInstance *) m_selectedInstance; + LegacyInstance *linst = (LegacyInstance *)m_selectedInstance; linst->setLWJGLVersion(lselect.selectedVersion()); } } @@ -617,18 +689,25 @@ void MainWindow::on_actionInstanceSettings_triggered() settings.exec(); } -void MainWindow::instanceChanged( const QModelIndex& current, const QModelIndex& previous ) +void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &previous) { - if(current.isValid() && nullptr != (m_selectedInstance = (BaseInstance *) current.data(InstanceList::InstancePointerRole).value<void *>())) + if (current.isValid() && + nullptr != (m_selectedInstance = + (BaseInstance *)current.data(InstanceList::InstancePointerRole) + .value<void *>())) { ui->instanceToolBar->setEnabled(true); QString iconKey = m_selectedInstance->iconKey(); renameButton->setText(m_selectedInstance->name()); - ui->actionChangeInstLWJGLVersion->setEnabled(m_selectedInstance->menuActionEnabled("actionChangeInstLWJGLVersion")); - ui->actionEditInstMods->setEnabled(m_selectedInstance->menuActionEnabled("actionEditInstMods")); + ui->actionChangeInstLWJGLVersion->setEnabled( + m_selectedInstance->menuActionEnabled("actionChangeInstLWJGLVersion")); + ui->actionEditInstMods->setEnabled( + m_selectedInstance->menuActionEnabled("actionEditInstMods")); + ui->actionChangeInstMCVersion->setEnabled( + m_selectedInstance->menuActionEnabled("actionChangeInstMCVersion")); statusBar()->clearMessage(); statusBar()->showMessage(m_selectedInstance->getStatusbarDescription()); - auto ico =MMC->icons()->getIcon(iconKey); + auto ico = MMC->icons()->getIcon(iconKey); ui->actionChangeInstIcon->setIcon(ico); } else @@ -643,24 +722,76 @@ void MainWindow::selectionBad() QString iconKey = "infinity"; statusBar()->clearMessage(); ui->instanceToolBar->setEnabled(false); - renameButton->setText("Rename Instance"); + renameButton->setText(tr("Rename Instance")); auto ico = MMC->icons()->getIcon(iconKey); ui->actionChangeInstIcon->setIcon(ico); } - - void MainWindow::on_actionEditInstNotes_triggered() { if (!m_selectedInstance) return; - LegacyInstance * linst = (LegacyInstance *) m_selectedInstance; - + LegacyInstance *linst = (LegacyInstance *)m_selectedInstance; + EditNotesDialog noteedit(linst->notes(), linst->name(), this); noteedit.exec(); if (noteedit.result() == QDialog::Accepted) { - + linst->setNotes(noteedit.getText()); } } + +void MainWindow::instanceEnded() +{ + this->show(); + ui->actionLaunchInstance->setEnabled(m_selectedInstance); +} + +void MainWindow::checkSetDefaultJava() +{ + bool askForJava = false; + { + QString currentHostName = QHostInfo::localHostName(); + QString oldHostName = MMC->settings()->get("LastHostname").toString(); + if (currentHostName != oldHostName) + { + MMC->settings()->set("LastHostname", currentHostName); + askForJava = true; + } + } + + { + QString currentJavaPath = MMC->settings()->get("JavaPath").toString(); + if (currentJavaPath.isEmpty()) + { + askForJava = true; + } + } + + if (askForJava) + { + QLOG_DEBUG() << "Java path needs resetting, showing Java selection dialog..."; + + JavaVersionPtr java; + + VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, + false); + vselect.setResizeOn(2); + vselect.exec(); + + if (!vselect.selectedVersion()) + { + QMessageBox::warning(this, tr("Invalid version selected"), + tr("You didn't select a valid Java version, so MultiMC will " + "select the default. " + "You can change this in the settings dialog.")); + + JavaUtils ju; + java = ju.GetDefaultJava(); + } + + java = std::dynamic_pointer_cast<JavaVersion>(vselect.selectedVersion()); + MMC->settings()->set("JavaPath", java->path); + } +} diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 3612dd0c..dbf7c4c3 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -3,7 +3,7 @@ * 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 @@ -19,7 +19,7 @@ #include <QMainWindow> #include "logic/lists/InstanceList.h" -#include "logic/tasks/LoginTask.h" +#include "logic/net/LoginTask.h" #include "logic/BaseInstance.h" class LabeledToolButton; @@ -39,112 +39,114 @@ class MainWindow; class MainWindow : public QMainWindow { Q_OBJECT - + public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); - + void closeEvent(QCloseEvent *event); // Browser Dialog void openWebPage(QUrl url); - - + + void checkSetDefaultJava(); + private slots: void onCatToggled(bool); - + void on_actionAbout_triggered(); - + void on_actionAddInstance_triggered(); - + void on_actionChangeInstGroup_triggered(); - + void on_actionChangeInstIcon_triggered(); - + void on_actionViewInstanceFolder_triggered(); - + void on_actionConfig_Folder_triggered(); - + void on_actionViewSelectedInstFolder_triggered(); void on_actionRefresh_triggered(); - + void on_actionViewCentralModsFolder_triggered(); - + void on_actionCheckUpdate_triggered(); - + void on_actionSettings_triggered(); - + void on_actionReportBug_triggered(); - + void on_actionNews_triggered(); - + void on_mainToolBar_visibilityChanged(bool); - - void on_instanceView_customContextMenuRequested(const QPoint &pos); - + + // void on_instanceView_customContextMenuRequested(const QPoint &pos); + void on_actionLaunchInstance_triggered(); - + void on_actionDeleteInstance_triggered(); - + void on_actionRenameInstance_triggered(); - + void on_actionMakeDesktopShortcut_triggered(); - + void on_actionChangeInstMCVersion_triggered(); - + void on_actionEditInstMods_triggered(); - + void on_actionEditInstNotes_triggered(); - - void doLogin(const QString& errorMsg = ""); - - + + void doLogin(const QString &errorMsg = ""); + void onLoginComplete(); - - + void onGameUpdateComplete(); void onGameUpdateError(QString error); - + void taskStart(); void taskEnd(); void on_actionChangeInstLWJGLVersion_triggered(); - - void on_actionInstanceSettings_triggered(); + + void instanceEnded(); + + void on_actionInstanceSettings_triggered(); public slots: - void instanceActivated ( QModelIndex ); + void instanceActivated(QModelIndex); + + void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous); - void instanceChanged (const QModelIndex & current,const QModelIndex & previous); - void selectionBad(); - + void startTask(Task *task); - + void launchInstance(BaseInstance *inst, LoginResponse response); protected: bool eventFilter(QObject *obj, QEvent *ev); void setCatBackground(bool enabled); + private: Ui::MainWindow *ui; - KCategoryDrawer * drawer; - KCategorizedView * view; - InstanceProxyModel * proxymodel; + KCategoryDrawer *drawer; + KCategorizedView *view; + InstanceProxyModel *proxymodel; MinecraftProcess *proc; ConsoleWindow *console; OneSixAssets *assets_downloader; - LabeledToolButton * renameButton; - + LabeledToolButton *renameButton; + BaseInstance *m_selectedInstance; - + // A pointer to the instance we are actively doing stuff with. // This is set when the user launches an instance and is used to refer to that // instance throughout the launching process. BaseInstance *m_activeInst; LoginResponse m_activeLogin; - + Task *m_versionLoadTask; }; diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index 4360f5f6..0f883d13 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -13,6 +13,10 @@ <property name="windowTitle"> <string>MultiMC 5</string> </property> + <property name="windowIcon"> + <iconset resource="../multimc.qrc"> + <normaloff>:/icons/multimc/scalable/apps/multimc.svg</normaloff>:/icons/multimc/scalable/apps/multimc.svg</iconset> + </property> <widget class="QWidget" name="centralWidget"> <layout class="QHBoxLayout" name="horizontalLayout"> <property name="spacing"> @@ -107,6 +111,7 @@ <addaction name="actionChangeInstGroup"/> <addaction name="separator"/> <addaction name="actionInstanceSettings"/> + <addaction name="actionChangeInstMCVersion"/> <addaction name="actionChangeInstLWJGLVersion"/> <addaction name="actionEditInstMods"/> <addaction name="actionViewSelectedInstFolder"/> @@ -371,9 +376,6 @@ </property> </action> <action name="actionChangeInstMCVersion"> - <property name="enabled"> - <bool>false</bool> - </property> <property name="text"> <string>Change Version</string> </property> @@ -440,7 +442,7 @@ <string>Meow</string> </property> <property name="toolTip"> - <string><html><head/><body><p align="center"><span style=" font-weight:600; color:#ff0004;">Catnatok!</span></p><p align="center">Or just a cat with a ball of yarn?</p><p align="center"><span style=" font-style:italic;">WHO KNOWS?!</span></p><p align="center"><img src=":/icons/instances/tnt"/></p></body></html></string> + <string><html><head/><body><p align="center"><span style=" font-weight:600; color:#ff0004;">Catnarok!</span></p><p align="center">Or just a cat with a ball of yarn?</p><p align="center"><span style=" font-style:italic;">WHO KNOWS?!</span></p><p align="center"><img src=":/icons/instances/tnt"/></p></body></html></string> </property> </action> </widget> diff --git a/gui/newinstancedialog.cpp b/gui/newinstancedialog.cpp index ac3bcd7d..c37db2ac 100644 --- a/gui/newinstancedialog.cpp +++ b/gui/newinstancedialog.cpp @@ -3,7 +3,7 @@ * 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 @@ -18,24 +18,23 @@ #include "ui_newinstancedialog.h" #include "logic/InstanceFactory.h" -#include "logic/InstanceVersion.h" +#include "logic/BaseVersion.h" #include "logic/lists/IconList.h" #include "logic/lists/MinecraftVersionList.h" #include "logic/tasks/Task.h" +#include "gui/platform.h" #include "versionselectdialog.h" -#include "taskdialog.h" +#include "ProgressDialog.h" #include "IconPickerDialog.h" #include <QLayout> #include <QPushButton> - - -NewInstanceDialog::NewInstanceDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::NewInstanceDialog) +NewInstanceDialog::NewInstanceDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::NewInstanceDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); resize(minimumSizeHint()); layout()->setSizeConstraint(QLayout::SetFixedSize); @@ -48,7 +47,7 @@ NewInstanceDialog::NewInstanceDialog(QWidget *parent) : taskDlg->exec(loadTask); } */ - setSelectedVersion(MinecraftVersionList::getMainList().getLatestStable()); + setSelectedVersion(MMC->minecraftlist()->getLatestStable()); InstIconKey = "infinity"; ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); } @@ -60,22 +59,23 @@ NewInstanceDialog::~NewInstanceDialog() void NewInstanceDialog::updateDialogState() { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!instName().isEmpty() && m_selectedVersion); + ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(!instName().isEmpty() && m_selectedVersion); } -void NewInstanceDialog::setSelectedVersion(InstVersionPtr version) +void NewInstanceDialog::setSelectedVersion(BaseVersionPtr version) { m_selectedVersion = version; - + if (m_selectedVersion) { - ui->versionTextBox->setText(version->name); + ui->versionTextBox->setText(version->name()); } else { ui->versionTextBox->setText(""); } - + updateDialogState(); } @@ -89,18 +89,19 @@ QString NewInstanceDialog::iconKey() const return InstIconKey; } -InstVersionPtr NewInstanceDialog::selectedVersion() const +BaseVersionPtr NewInstanceDialog::selectedVersion() const { return m_selectedVersion; } void NewInstanceDialog::on_btnChangeVersion_clicked() { - VersionSelectDialog vselect(&MinecraftVersionList::getMainList(), this); + VersionSelectDialog vselect(MMC->minecraftlist().get(), tr("Change Minecraft version"), + this); vselect.exec(); if (vselect.result() == QDialog::Accepted) { - InstVersionPtr version = vselect.selectedVersion(); + BaseVersionPtr version = vselect.selectedVersion(); if (version) setSelectedVersion(version); } @@ -110,8 +111,8 @@ void NewInstanceDialog::on_iconButton_clicked() { IconPickerDialog dlg(this); dlg.exec(InstIconKey); - - if(dlg.result() == QDialog::Accepted) + + if (dlg.result() == QDialog::Accepted) { InstIconKey = dlg.selectedIconKey; ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); diff --git a/gui/newinstancedialog.h b/gui/newinstancedialog.h index e8c57024..408cf757 100644 --- a/gui/newinstancedialog.h +++ b/gui/newinstancedialog.h @@ -17,7 +17,7 @@ #define NEWINSTANCEDIALOG_H #include <QDialog> -#include "logic/InstanceVersion.h" +#include "logic/BaseVersion.h" namespace Ui { class NewInstanceDialog; @@ -33,13 +33,13 @@ public: void updateDialogState(); - void setSelectedVersion(InstVersionPtr version); + void setSelectedVersion(BaseVersionPtr version); void loadVersionList(); QString instName() const; QString iconKey() const; - InstVersionPtr selectedVersion() const; + BaseVersionPtr selectedVersion() const; private slots: void on_btnChangeVersion_clicked(); @@ -49,7 +49,7 @@ private slots: private: Ui::NewInstanceDialog *ui; - InstVersionPtr m_selectedVersion; + BaseVersionPtr m_selectedVersion; QString InstIconKey; }; diff --git a/gui/platform.h b/gui/platform.h new file mode 100644 index 00000000..5cf9ed80 --- /dev/null +++ b/gui/platform.h @@ -0,0 +1,35 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PLATFORM_H +#define PLATFORM_H + +/** + * @file platform.h + * This file contains platform-specific functions, tweaks and fixes. + */ + +#include <QWidget> + +class MultiMCPlatform +{ +public: + // X11 WM_CLASS + static void fixWM_CLASS(QWidget *widget); +}; + +#endif // PLATFORM_H diff --git a/gui/platform_other.cpp b/gui/platform_other.cpp new file mode 100644 index 00000000..a41abe36 --- /dev/null +++ b/gui/platform_other.cpp @@ -0,0 +1,27 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gui/platform.h> +/** + * Stub for non-X11 platforms + * @brief MultiMCPlatform::fixWM_CLASS + * @param widget + */ +void MultiMCPlatform::fixWM_CLASS(QWidget *widget) +{ + Q_UNUSED(widget); +} diff --git a/gui/platform_x11.cpp b/gui/platform_x11.cpp new file mode 100644 index 00000000..0401e8bf --- /dev/null +++ b/gui/platform_x11.cpp @@ -0,0 +1,62 @@ +/* Copyright 2013 MultiMC Contributors + * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gui/platform.h> +#include <QtX11Extras/QX11Info> +#include <xcb/xcb.h> + +static QByteArray WM_CLASS = "MultiMC5\0MultiMC5"; + +template <typename... ArgTypes, typename... ArgTypes2> +static inline unsigned int XcbCallVoid(xcb_void_cookie_t (*func)(xcb_connection_t *, ArgTypes...), ArgTypes2... args...) +{ + return func(QX11Info::connection(), args...).sequence; +} + +static void getAtoms(size_t n, xcb_atom_t *atoms, const char *const names[], bool create) +{ + xcb_connection_t *conn = QX11Info::connection(); + xcb_intern_atom_cookie_t *cookies = (xcb_intern_atom_cookie_t *)malloc(sizeof(xcb_intern_atom_cookie_t) * 2); + for (size_t i = 0; i < n; ++i) + cookies[i] = xcb_intern_atom(conn, create, strlen(names[i]), names[i]); + memset(atoms, 0, sizeof(xcb_atom_t) * n); + for (size_t i = 0; i < n; ++i) + { + xcb_intern_atom_reply_t *r = xcb_intern_atom_reply(conn, cookies[i], 0); + if (r) + { + atoms[i] = r->atom; + free(r); + } + } + free(cookies); +} + +static inline xcb_atom_t getAtom(const char *name, bool create=false) +{ + xcb_atom_t atom; + getAtoms(1, &atom, &name, create); + return atom; +} + +void MultiMCPlatform::fixWM_CLASS(QWidget *widget) +{ + static const xcb_atom_t atom = getAtom("WM_CLASS"); + XcbCallVoid(xcb_change_property, XCB_PROP_MODE_REPLACE, + widget->winId(), atom, XCB_ATOM_STRING, 8, WM_CLASS.count(), + WM_CLASS.constData()); +} diff --git a/gui/settingsdialog.cpp b/gui/settingsdialog.cpp index a1fbb8e7..14eba492 100644 --- a/gui/settingsdialog.cpp +++ b/gui/settingsdialog.cpp @@ -13,10 +13,14 @@ * limitations under the License. */ +#include <MultiMC.h> #include "settingsdialog.h" #include "ui_settingsdialog.h" +#include "logic/JavaUtils.h" +#include "gui/versionselectdialog.h" +#include "gui/platform.h" +#include "logic/lists/JavaVersionList.h" -#include <MultiMC.h> #include <settingsobject.h> #include <QFileDialog> #include <QMessageBox> @@ -25,9 +29,10 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::SettingsDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); - loadSettings(MMC->settings()); + loadSettings(MMC->settings().get()); updateCheckboxStuff(); } @@ -49,7 +54,7 @@ void SettingsDialog::updateCheckboxStuff() void SettingsDialog::on_instDirBrowseBtn_clicked() { - QString dir = QFileDialog::getExistingDirectory(this, "Instance Directory", + QString dir = QFileDialog::getExistingDirectory(this, tr("Instance Directory"), ui->instDirTextBox->text()); if (!dir.isEmpty()) ui->instDirTextBox->setText(dir); @@ -57,7 +62,7 @@ void SettingsDialog::on_instDirBrowseBtn_clicked() void SettingsDialog::on_modsDirBrowseBtn_clicked() { - QString dir = QFileDialog::getExistingDirectory(this, "Mods Directory", + QString dir = QFileDialog::getExistingDirectory(this, tr("Mods Directory"), ui->modsDirTextBox->text()); if (!dir.isEmpty()) ui->modsDirTextBox->setText(dir); @@ -65,7 +70,7 @@ void SettingsDialog::on_modsDirBrowseBtn_clicked() void SettingsDialog::on_lwjglDirBrowseBtn_clicked() { - QString dir = QFileDialog::getExistingDirectory(this, "LWJGL Directory", + QString dir = QFileDialog::getExistingDirectory(this, tr("LWJGL Directory"), ui->lwjglDirTextBox->text()); if (!dir.isEmpty()) ui->lwjglDirTextBox->setText(dir); @@ -85,7 +90,7 @@ void SettingsDialog::on_maximizedCheckBox_clicked(bool checked) void SettingsDialog::on_buttonBox_accepted() { - applySettings(MMC->settings()); + applySettings(MMC->settings().get()); } void SettingsDialog::applySettings(SettingsObject *s) @@ -99,9 +104,9 @@ void SettingsDialog::applySettings(SettingsObject *s) } else if (!s->get("UseDevBuilds").toBool()) { - int response = QMessageBox::question(this, "Development builds", - "Development builds contain experimental features " - "and may be unstable. Are you sure you want to enable them?"); + int response = QMessageBox::question(this, tr("Development builds"), + tr("Development builds contain experimental features " + "and may be unstable. Are you sure you want to enable them?")); if (response == QMessageBox::Yes) { s->set("UseDevBuilds", true); @@ -180,3 +185,27 @@ void SettingsDialog::loadSettings(SettingsObject *s) ui->preLaunchCmdTextBox->setText(s->get("PreLaunchCommand").toString()); ui->postExitCmdTextBox->setText(s->get("PostExitCommand").toString()); } + +void SettingsDialog::on_pushButton_clicked() +{ + JavaVersionPtr java; + + VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); + vselect.setResizeOn(2); + vselect.exec(); + + if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) + { + java = std::dynamic_pointer_cast<JavaVersion>(vselect.selectedVersion()); + ui->javaPathTextBox->setText(java->path); + } +} + +void SettingsDialog::on_btnBrowse_clicked() +{ + QString dir = QFileDialog::getOpenFileName(this, tr("Find Java executable")); + if(!dir.isNull()) + { + ui->javaPathTextBox->setText(dir); + } +} diff --git a/gui/settingsdialog.h b/gui/settingsdialog.h index b0a8c673..a8dfb1c6 100644 --- a/gui/settingsdialog.h +++ b/gui/settingsdialog.h @@ -53,6 +53,10 @@ private slots: void on_buttonBox_accepted(); + void on_pushButton_clicked(); + + void on_btnBrowse_clicked(); + private: Ui::SettingsDialog *ui; }; diff --git a/gui/settingsdialog.ui b/gui/settingsdialog.ui index 0d30e301..bf173b1b 100644 --- a/gui/settingsdialog.ui +++ b/gui/settingsdialog.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>453</width> - <height>550</height> + <width>502</width> + <height>599</height> </rect> </property> <property name="sizePolicy"> @@ -353,7 +353,7 @@ <number>64</number> </property> <property name="maximum"> - <number>512</number> + <number>999999999</number> </property> <property name="singleStep"> <number>8</number> @@ -374,29 +374,60 @@ <layout class="QGridLayout" name="gridLayout_3"> <item row="0" column="0"> <widget class="QLabel" name="labelJavaPath"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> <string>Java path:</string> </property> </widget> </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="javaPathTextBox"/> - </item> - <item row="1" column="0"> + <item row="2" column="0"> <widget class="QLabel" name="labelJVMArgs"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> <string>JVM arguments:</string> </property> </widget> </item> - <item row="0" column="2"> + <item row="1" column="3"> + <widget class="QPushButton" name="btnBrowse"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Browse...</string> + </property> + </widget> + </item> + <item row="0" column="1" colspan="3"> + <widget class="QLineEdit" name="javaPathTextBox"/> + </item> + <item row="1" column="2"> <widget class="QPushButton" name="pushButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> - <string>Auto-detect</string> + <string>Auto-detect...</string> </property> </widget> </item> - <item row="1" column="1" colspan="2"> + <item row="2" column="1" colspan="3"> <widget class="QLineEdit" name="jvmArgsTextBox"/> </item> </layout> @@ -466,6 +497,35 @@ </item> </layout> </widget> + <tabstops> + <tabstop>settingsTabs</tabstop> + <tabstop>buttonBox</tabstop> + <tabstop>sortLastLaunchedBtn</tabstop> + <tabstop>sortByNameBtn</tabstop> + <tabstop>devBuildsCheckBox</tabstop> + <tabstop>autoUpdateCheckBox</tabstop> + <tabstop>instDirTextBox</tabstop> + <tabstop>modsDirTextBox</tabstop> + <tabstop>lwjglDirTextBox</tabstop> + <tabstop>instDirBrowseBtn</tabstop> + <tabstop>modsDirBrowseBtn</tabstop> + <tabstop>lwjglDirBrowseBtn</tabstop> + <tabstop>maximizedCheckBox</tabstop> + <tabstop>windowWidthSpinBox</tabstop> + <tabstop>windowHeightSpinBox</tabstop> + <tabstop>showConsoleCheck</tabstop> + <tabstop>autoCloseConsoleCheck</tabstop> + <tabstop>autoLoginCheckBox</tabstop> + <tabstop>minMemSpinBox</tabstop> + <tabstop>maxMemSpinBox</tabstop> + <tabstop>permGenSpinBox</tabstop> + <tabstop>javaPathTextBox</tabstop> + <tabstop>pushButton</tabstop> + <tabstop>btnBrowse</tabstop> + <tabstop>jvmArgsTextBox</tabstop> + <tabstop>preLaunchCmdTextBox</tabstop> + <tabstop>postExitCmdTextBox</tabstop> + </tabstops> <resources> <include location="../multimc.qrc"/> </resources> diff --git a/gui/versionselectdialog.cpp b/gui/versionselectdialog.cpp index 66d772b0..8fa62f75 100644 --- a/gui/versionselectdialog.cpp +++ b/gui/versionselectdialog.cpp @@ -3,7 +3,7 @@ * 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 @@ -20,32 +20,34 @@ #include <QDebug> -#include <gui/taskdialog.h> +#include <gui/ProgressDialog.h> +#include "gui/platform.h" -#include <logic/InstanceVersion.h> -#include <logic/lists/InstVersionList.h> +#include <logic/BaseVersion.h> +#include <logic/lists/BaseVersionList.h> #include <logic/tasks/Task.h> -VersionSelectDialog::VersionSelectDialog(InstVersionList *vlist, QWidget *parent) : - QDialog(parent), - ui(new Ui::VersionSelectDialog) +VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, bool cancelable) + : QDialog(parent), ui(new Ui::VersionSelectDialog) { + MultiMCPlatform::fixWM_CLASS(this); ui->setupUi(this); setWindowModality(Qt::WindowModal); - + setWindowTitle(title); + m_vlist = vlist; - + m_proxyModel = new QSortFilterProxyModel(this); m_proxyModel->setSourceModel(vlist); - + ui->listView->setModel(m_proxyModel); ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - ui->listView->header()->setSectionResizeMode(0, QHeaderView::Stretch); - - connect(ui->filterSnapshotsCheckbox, SIGNAL(clicked()), SLOT(updateFilterState())); - connect(ui->filterMCNostalgiaCheckbox, SIGNAL(clicked()), SLOT(updateFilterState())); - - updateFilterState(); + ui->listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); + + if(!cancelable) + { + ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); + } } VersionSelectDialog::~VersionSelectDialog() @@ -53,6 +55,13 @@ VersionSelectDialog::~VersionSelectDialog() delete ui; } +void VersionSelectDialog::setResizeOn(int column) +{ + ui->listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::ResizeToContents); + resizeOnColumn = column; + ui->listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); +} + int VersionSelectDialog::exec() { QDialog::open(); @@ -63,17 +72,17 @@ int VersionSelectDialog::exec() void VersionSelectDialog::loadList() { - TaskDialog *taskDlg = new TaskDialog(this); + ProgressDialog *taskDlg = new ProgressDialog(this); Task *loadTask = m_vlist->getLoadTask(); loadTask->setParent(taskDlg); taskDlg->exec(loadTask); } -InstVersionPtr VersionSelectDialog::selectedVersion() const +BaseVersionPtr VersionSelectDialog::selectedVersion() const { auto currentIndex = ui->listView->selectionModel()->currentIndex(); - auto variant = m_proxyModel->data(currentIndex, InstVersionList::VersionPointerRole); - return variant.value<InstVersionPtr>(); + auto variant = m_proxyModel->data(currentIndex, BaseVersionList::VersionPointerRole); + return variant.value<BaseVersionPtr>(); } void VersionSelectDialog::on_refreshButton_clicked() @@ -81,21 +90,21 @@ void VersionSelectDialog::on_refreshButton_clicked() loadList(); } -void VersionSelectDialog::updateFilterState() +void VersionSelectDialog::setFilter(int column, QString filter) { - m_proxyModel->setFilterKeyColumn(InstVersionList::TypeColumn); - + m_proxyModel->setFilterKeyColumn(column); + m_proxyModel->setFilterFixedString(filter); + /* QStringList filteredTypes; if (!ui->filterSnapshotsCheckbox->isChecked()) filteredTypes += "Snapshot"; if (!ui->filterMCNostalgiaCheckbox->isChecked()) filteredTypes += "Nostalgia"; - + QString regexStr = "^.*$"; if (filteredTypes.length() > 0) regexStr = QString("^((?!%1).)*$").arg(filteredTypes.join('|')); - - qDebug() << "Filter:" << regexStr; - - m_proxyModel->setFilterRegExp(regexStr); + + QLOG_DEBUG() << "Filter:" << regexStr; + */ } diff --git a/gui/versionselectdialog.h b/gui/versionselectdialog.h index b864aee1..319caeca 100644 --- a/gui/versionselectdialog.h +++ b/gui/versionselectdialog.h @@ -19,9 +19,9 @@ #include <QDialog> #include <QSortFilterProxyModel> -#include "logic/InstanceVersion.h" +#include "logic/BaseVersion.h" -class InstVersionList; +class BaseVersionList; namespace Ui { @@ -33,7 +33,7 @@ class VersionSelectDialog : public QDialog Q_OBJECT public: - explicit VersionSelectDialog(InstVersionList *vlist, QWidget *parent = 0); + explicit VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent = 0, bool cancelable = true); ~VersionSelectDialog(); virtual int exec(); @@ -41,19 +41,21 @@ public: //! Starts a task that loads the list. void loadList(); - InstVersionPtr selectedVersion() const; + BaseVersionPtr selectedVersion() const; + + void setFilter(int column, QString filter); + void setResizeOn(int column); private slots: void on_refreshButton_clicked(); - - void updateFilterState(); - private: Ui::VersionSelectDialog *ui; - InstVersionList *m_vlist; + BaseVersionList *m_vlist; QSortFilterProxyModel *m_proxyModel; + + int resizeOnColumn = 0; }; #endif // VERSIONSELECTDIALOG_H diff --git a/gui/versionselectdialog.ui b/gui/versionselectdialog.ui index 02937794..222f29cf 100644 --- a/gui/versionselectdialog.ui +++ b/gui/versionselectdialog.ui @@ -40,75 +40,6 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="filterCheckboxLayout1"> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QCheckBox" name="filterSnapshotsCheckbox"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Show &snapshots?</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QCheckBox" name="filterMCNostalgiaCheckbox"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Show &Nostalgia?</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QPushButton" name="refreshButton"> diff --git a/logger/QsDebugOutput.cpp b/logger/QsDebugOutput.cpp new file mode 100644 index 00000000..d68cd5e9 --- /dev/null +++ b/logger/QsDebugOutput.cpp @@ -0,0 +1,52 @@ +// Copyright (c) 2010, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "QsDebugOutput.h" +#include <QString> +#include <QtGlobal> + +#if defined(Q_OS_WIN) +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +void QsDebugOutput::output(const QString &message) +{ + OutputDebugStringW(reinterpret_cast<const WCHAR *>(message.utf16())); + OutputDebugStringW(L"\n"); +} +#elif defined(Q_OS_SYMBIAN) +#include <e32debug.h> +void QsDebugOutput::output(const QString &message) +{ + TPtrC8 symbianMessage(reinterpret_cast<const TUint8 *>(qPrintable(message))); + RDebug::RawPrint(symbianMessage); +} +#elif defined(Q_OS_UNIX) +#include <cstdio> +void QsDebugOutput::output(const QString &message) +{ + fprintf(stderr, "%s\n", qPrintable(message)); + fflush(stderr); +} +#endif diff --git a/logger/QsDebugOutput.h b/logger/QsDebugOutput.h new file mode 100644 index 00000000..8c759a6d --- /dev/null +++ b/logger/QsDebugOutput.h @@ -0,0 +1,34 @@ +// Copyright (c) 2010, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +class QString; + +class QsDebugOutput +{ +public: + static void output(const QString &a_message); +}; diff --git a/logger/QsLog.cpp b/logger/QsLog.cpp new file mode 100644 index 00000000..8cf68a53 --- /dev/null +++ b/logger/QsLog.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2010, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "QsLog.h" +#include "QsLogDest.h" +#include <QMutex> +#include <QList> +#include <QDateTime> +#include <QtGlobal> +#include <cassert> +#include <cstdlib> +#include <stdexcept> + +namespace QsLogging +{ +typedef QList<Destination *> DestinationList; + +static const char *LevelStrings[] = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" + "UNKNOWN"}; + +// not using Qt::ISODate because we need the milliseconds too +static const QString fmtDateTime("hhhh:mm:ss.zzz"); + +static const char *LevelToText(Level theLevel) +{ + if (theLevel > FatalLevel) + { + assert(!"bad log level"); + return LevelStrings[UnknownLevel]; + } + return LevelStrings[theLevel]; +} + +class LoggerImpl +{ +public: + LoggerImpl() : level(InfoLevel) + { + } + QMutex logMutex; + Level level; + DestinationList destList; +}; + +Logger::Logger() : d(new LoggerImpl) +{ +} + +Logger::~Logger() +{ + delete d; +} + +void Logger::addDestination(Destination *destination) +{ + assert(destination); + d->destList.push_back(destination); +} + +void Logger::setLoggingLevel(Level newLevel) +{ + d->level = newLevel; +} + +Level Logger::loggingLevel() const +{ + return d->level; +} + +//! creates the complete log message and passes it to the logger +void Logger::Helper::writeToLog() +{ + const char *const levelName = LevelToText(level); + const QString completeMessage(QString("%1\t%2").arg(levelName, 5).arg(buffer)); + + Logger &logger = Logger::instance(); + QMutexLocker lock(&logger.d->logMutex); + logger.write(completeMessage); +} + +Logger::Helper::Helper(Level logLevel) : level(logLevel), qtDebug(&buffer) +{ +} + +Logger::Helper::~Helper() +{ + try + { + writeToLog(); + } + catch (std::exception &e) + { + // you shouldn't throw exceptions from a sink + Q_UNUSED(e); + assert(!"exception in logger helper destructor"); + throw; + } +} + +//! sends the message to all the destinations +void Logger::write(const QString &message) +{ + for (DestinationList::iterator it = d->destList.begin(), endIt = d->destList.end(); + it != endIt; ++it) + { + if (!(*it)) + { + assert(!"null log destination"); + continue; + } + (*it)->write(message); + } +} + +} // end namespace diff --git a/logger/QsLog.h b/logger/QsLog.h new file mode 100644 index 00000000..a18c08de --- /dev/null +++ b/logger/QsLog.h @@ -0,0 +1,130 @@ +// Copyright (c) 2010, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <QDebug> +#include <QString> + +namespace QsLogging +{ +class Destination; +enum Level +{ + TraceLevel = 0, + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + FatalLevel, + UnknownLevel +}; + +class LoggerImpl; // d pointer +class Logger +{ +public: + static Logger &instance() + { + static Logger staticLog; + return staticLog; + } + + //! Adds a log message destination. Don't add null destinations. + void addDestination(Destination *destination); + //! Logging at a level < 'newLevel' will be ignored + void setLoggingLevel(Level newLevel); + //! The default level is INFO + Level loggingLevel() const; + + //! The helper forwards the streaming to QDebug and builds the final + //! log message. + class Helper + { + public: + explicit Helper(Level logLevel); + ~Helper(); + QDebug &stream() + { + return qtDebug; + } + + private: + void writeToLog(); + + Level level; + QString buffer; + QDebug qtDebug; + }; + +private: + Logger(); + Logger(const Logger &); + Logger &operator=(const Logger &); + ~Logger(); + + void write(const QString &message); + + LoggerImpl *d; +}; + +} // end namespace + +#define QLOG_TRACE() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::TraceLevel) \ + QsLogging::Logger::Helper(QsLogging::TraceLevel).stream() +#define QLOG_DEBUG() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::DebugLevel) \ + QsLogging::Logger::Helper(QsLogging::DebugLevel).stream() +#define QLOG_INFO() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::InfoLevel) \ + QsLogging::Logger::Helper(QsLogging::InfoLevel).stream() +#define QLOG_WARN() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::WarnLevel) \ + QsLogging::Logger::Helper(QsLogging::WarnLevel).stream() +#define QLOG_ERROR() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::ErrorLevel) \ + QsLogging::Logger::Helper(QsLogging::ErrorLevel).stream() +#define QLOG_FATAL() QsLogging::Logger::Helper(QsLogging::FatalLevel).stream() + +/* +#define QLOG_TRACE() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::TraceLevel) \ + QsLogging::Logger::Helper(QsLogging::TraceLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_DEBUG() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::DebugLevel) \ + QsLogging::Logger::Helper(QsLogging::DebugLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_INFO() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::InfoLevel) \ + QsLogging::Logger::Helper(QsLogging::InfoLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_WARN() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::WarnLevel) \ + QsLogging::Logger::Helper(QsLogging::WarnLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_ERROR() \ + if (QsLogging::Logger::instance().loggingLevel() <= QsLogging::ErrorLevel) \ + QsLogging::Logger::Helper(QsLogging::ErrorLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_FATAL() \ + QsLogging::Logger::Helper(QsLogging::FatalLevel).stream() << __FILE__ << '@' << __LINE__ +*/
\ No newline at end of file diff --git a/logger/QsLogDest.cpp b/logger/QsLogDest.cpp new file mode 100644 index 00000000..36297a14 --- /dev/null +++ b/logger/QsLogDest.cpp @@ -0,0 +1,83 @@ +// Copyright (c) 2010, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "QsLogDest.h" +#include "QsDebugOutput.h" +#include <QFile> +#include <QTextStream> +#include <QString> + +namespace QsLogging +{ + +//! file message sink +class FileDestination : public Destination +{ +public: + FileDestination(const QString &filePath); + virtual void write(const QString &message); + +private: + QFile mFile; + QTextStream mOutputStream; +}; + +FileDestination::FileDestination(const QString &filePath) +{ + mFile.setFileName(filePath); + mFile.open(QFile::WriteOnly | QFile::Text | + QFile::Truncate); // fixme: should throw on failure + mOutputStream.setDevice(&mFile); +} + +void FileDestination::write(const QString &message) +{ + mOutputStream << message << endl; + mOutputStream.flush(); +} + +//! debugger sink +class DebugOutputDestination : public Destination +{ +public: + virtual void write(const QString &message); +}; + +void DebugOutputDestination::write(const QString &message) +{ + QsDebugOutput::output(message); +} + +DestinationPtr DestinationFactory::MakeFileDestination(const QString &filePath) +{ + return DestinationPtr(new FileDestination(filePath)); +} + +DestinationPtr DestinationFactory::MakeDebugOutputDestination() +{ + return DestinationPtr(new DebugOutputDestination); +} + +} // end namespace diff --git a/logger/QsLogDest.h b/logger/QsLogDest.h new file mode 100644 index 00000000..32f1a9d0 --- /dev/null +++ b/logger/QsLogDest.h @@ -0,0 +1,53 @@ +// Copyright (c) 2010, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <memory> +class QString; + +namespace QsLogging +{ + +class Destination +{ +public: + virtual ~Destination() + { + } + virtual void write(const QString &message) = 0; +}; +typedef std::shared_ptr<Destination> DestinationPtr; + +//! Creates logging destinations/sinks. The caller will have ownership of +//! the newly created destinations. +class DestinationFactory +{ +public: + static DestinationPtr MakeFileDestination(const QString &filePath); + static DestinationPtr MakeDebugOutputDestination(); +}; + +} // end namespace diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index e166449f..6a6b195b 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -13,6 +13,7 @@ * limitations under the License. */ +#include "MultiMC.h" #include "BaseInstance.h" #include "BaseInstance_p.h" @@ -131,9 +132,9 @@ InstanceList *BaseInstance::instList() const return NULL; } -InstVersionList *BaseInstance::versionList() const +std::shared_ptr<BaseVersionList> BaseInstance::versionList() const { - return &MinecraftVersionList::getMainList(); + return MMC->minecraftlist(); } SettingsObject &BaseInstance::settings() const diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index cc9422be..b083c24a 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -3,7 +3,7 @@ * 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 @@ -21,7 +21,8 @@ #include <settingsobject.h> #include "inifile.h" -#include "lists/InstVersionList.h" +#include "lists/BaseVersionList.h" +#include "net/LoginTask.h" class QDialog; class BaseUpdate; @@ -32,9 +33,9 @@ class BaseInstancePrivate; /*! * \brief Base class for instances. - * This class implements many functions that are common between instances and + * 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. */ @@ -43,66 +44,72 @@ class BaseInstance : public QObject Q_OBJECT protected: /// no-touchy! - BaseInstance(BaseInstancePrivate * d, const QString &rootDir, SettingsObject * settings, QObject *parent = 0); + BaseInstance(BaseInstancePrivate *d, const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + public: /// virtual destructor to make sure the destruction is COMPLETE virtual ~BaseInstance() {}; - - /// nuke thoroughly - deletes the instance contents, notifies the list/model which is responsible of cleaning up the husk + + /// 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. + + /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to + /// be unique. QString id() const; - + /// get the type of this instance QString instanceType() const; - + /// Path to the instance's root directory. QString instanceRoot() const; - + /// Path to the instance's minecraft directory. QString minecraftRoot() const; - + QString name() const; void setName(QString val); - + 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); - - + virtual QString intendedVersionId() const = 0; virtual bool setIntendedVersionId(QString version) = 0; - + + virtual bool versionIsCustom() = 0; + /*! * The instance's current version. - * This value represents the instance's current version. If this value is + * 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; - //virtual void setCurrentVersionId(QString val) = 0; - + // virtual void setCurrentVersionId(QString val) = 0; + /*! * Whether or not Minecraft should be downloaded when the instance is launched. */ virtual bool shouldUpdate() const = 0; virtual void setShouldUpdate(bool val) = 0; - /// Get the curent base jar of this instance. By default, it's the versions/$version/$version.jar + /// Get the curent base jar of this instance. By default, it's the + /// versions/$version/$version.jar QString baseJar() const; /// the default base jar of this instance virtual QString defaultBaseJar() const = 0; /// the default custom base jar of this instance virtual QString defaultCustomBaseJar() const = 0; - + /*! * Whether or not custom base jar is used */ @@ -113,7 +120,7 @@ public: */ QString customBaseJar() const; void setCustomBaseJar(QString val); - + /** * Gets the time that the instance was last launched. * Stored in milliseconds since epoch. @@ -121,53 +128,54 @@ public: qint64 lastLaunch() const; /// Sets the last launched time to 'val' milliseconds since epoch void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch()); - + /*! - * \brief Gets the instance list that this instance is a part of. - * Returns NULL if this instance is not in a list + * \brief Gets the instance list that this instance is a part of. + * Returns NULL if this instance is not in a list * (the parent is not an InstanceList). - * \return A pointer to the InstanceList containing this instance. + * \return A pointer to the InstanceList containing this instance. */ InstanceList *instList() const; - + /*! * \brief Gets a pointer to this instance's version list. * \return A pointer to the available version list for this instance. */ - virtual InstVersionList *versionList() const; - + virtual std::shared_ptr<BaseVersionList> versionList() const; + /*! * \brief Gets this instance's settings object. * This settings object stores instance-specific settings. * \return A pointer to this instance's settings object. */ virtual SettingsObject &settings() const; - + /// returns a valid update task if update is needed, NULL otherwise - virtual BaseUpdate* doUpdate() = 0; - + virtual BaseUpdate *doUpdate() = 0; + /// returns a valid minecraft process, ready for launch - virtual MinecraftProcess* prepareForLaunch(QString user, QString session) = 0; - - /// do any necessary cleanups after the instance finishes. also runs before 'prepareForLaunch' + virtual MinecraftProcess *prepareForLaunch(LoginResponse response) = 0; + + /// do any necessary cleanups after the instance finishes. also runs before + /// 'prepareForLaunch' virtual void cleanupAfterRun() = 0; - + /// create a mod edit dialog for the instance - virtual QDialog * createModEditDialog ( QWidget* parent ) = 0; - + virtual QDialog *createModEditDialog(QWidget *parent) = 0; + /// is a particular action enabled with this instance selected? virtual bool menuActionEnabled(QString action_name) const = 0; - + virtual QString getStatusbarDescription() = 0; - + /// FIXME: this really should be elsewhere... virtual QString instanceConfigFolder() const = 0; - + signals: /*! * \brief Signal emitted when properties relevant to the instance view change */ - void propertiesChanged(BaseInstance * inst); + void propertiesChanged(BaseInstance *inst); /*! * \brief Signal emitted when groups are affected in any way */ @@ -175,12 +183,11 @@ signals: /*! * \brief The instance just got nuked. Hurray! */ - void nuked(BaseInstance * inst); - + void nuked(BaseInstance *inst); + protected: - QSharedPointer<BaseInstancePrivate> inst_d; + std::shared_ptr<BaseInstancePrivate> inst_d; }; // pointer for lazy people -typedef QSharedPointer<BaseInstance> InstancePtr; - +typedef std::shared_ptr<BaseInstance> InstancePtr; diff --git a/logic/BaseInstance_p.h b/logic/BaseInstance_p.h index a30916a4..06c0c0ba 100644 --- a/logic/BaseInstance_p.h +++ b/logic/BaseInstance_p.h @@ -4,7 +4,7 @@ class BaseInstance; -#define I_D(Class) Class##Private * const d = (Class##Private * const) inst_d.data() +#define I_D(Class) Class##Private * const d = (Class##Private * const) inst_d.get() struct BaseInstancePrivate { diff --git a/logic/BaseUpdate.cpp b/logic/BaseUpdate.cpp index b086ab14..02b29d32 100644 --- a/logic/BaseUpdate.cpp +++ b/logic/BaseUpdate.cpp @@ -7,7 +7,5 @@ BaseUpdate::BaseUpdate ( BaseInstance* inst, QObject* parent ) : Task ( parent ) void BaseUpdate::updateDownloadProgress(qint64 current, qint64 total) { - // The progress on the current file is current / total - float currentDLProgress = (float) current / (float) total; - setProgress((int)(currentDLProgress * 100)); // convert to percentage + emit progress(current, total); }
\ No newline at end of file diff --git a/logic/BaseVersion.h b/logic/BaseVersion.h new file mode 100644 index 00000000..01745c46 --- /dev/null +++ b/logic/BaseVersion.h @@ -0,0 +1,45 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include <memory> + +/*! + * An abstract base class for versions. + */ +struct 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; +}; + +typedef std::shared_ptr<BaseVersion> BaseVersionPtr; + +Q_DECLARE_METATYPE( BaseVersionPtr )
\ No newline at end of file diff --git a/logic/EnabledItemFilter.cpp b/logic/EnabledItemFilter.cpp new file mode 100644 index 00000000..6ecd0271 --- /dev/null +++ b/logic/EnabledItemFilter.cpp @@ -0,0 +1,30 @@ +#include "EnabledItemFilter.h" + +EnabledItemFilter::EnabledItemFilter(QObject* parent) + :QSortFilterProxyModel(parent) +{ + +} + +void EnabledItemFilter::setActive(bool active) +{ + m_active = active; + invalidateFilter(); +} + +bool EnabledItemFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if(!m_active) + return true; + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + if(sourceModel()->flags(index) & Qt::ItemIsEnabled) + { + return true; + } + return false; +} + +bool EnabledItemFilter::lessThan(const QModelIndex& left, const QModelIndex& right) const +{ + return QSortFilterProxyModel::lessThan(left, right); +} diff --git a/logic/EnabledItemFilter.h b/logic/EnabledItemFilter.h new file mode 100644 index 00000000..cb6d4041 --- /dev/null +++ b/logic/EnabledItemFilter.h @@ -0,0 +1,16 @@ +#pragma once +#include <QSortFilterProxyModel> + +class EnabledItemFilter : public QSortFilterProxyModel +{ + Q_OBJECT +public: + EnabledItemFilter(QObject *parent = 0); + void setActive(bool active); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +private: + bool m_active = false; +};
\ No newline at end of file diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp new file mode 100644 index 00000000..a946dd44 --- /dev/null +++ b/logic/ForgeInstaller.cpp @@ -0,0 +1,140 @@ +#include "ForgeInstaller.h" +#include "OneSixVersion.h" +#include "OneSixLibrary.h" +#include "net/HttpMetaCache.h" +#include <quazip.h> +#include <quazipfile.h> +#include <pathutils.h> +#include <QStringList> +#include "MultiMC.h" + +ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) +{ + std::shared_ptr<OneSixVersion> newVersion; + m_universal_url = universal_url; + + QuaZip zip(filename); + if (!zip.open(QuaZip::mdUnzip)) + return; + + QuaZipFile file(&zip); + + // read the install profile + if (!zip.setCurrentFile("install_profile.json")) + return; + + QJsonParseError jsonError; + if (!file.open(QIODevice::ReadOnly)) + return; + QJsonDocument jsonDoc = QJsonDocument::fromJson(file.readAll(), &jsonError); + file.close(); + if (jsonError.error != QJsonParseError::NoError) + return; + + if (!jsonDoc.isObject()) + return; + + QJsonObject root = jsonDoc.object(); + + auto installVal = root.value("install"); + auto versionInfoVal = root.value("versionInfo"); + if (!installVal.isObject() || !versionInfoVal.isObject()) + return; + + // read the forge version info + { + newVersion = OneSixVersion::fromJson(versionInfoVal.toObject()); + if (!newVersion) + return; + } + + QJsonObject installObj = installVal.toObject(); + QString libraryName = installObj.value("path").toString(); + internalPath = installObj.value("filePath").toString(); + + // where do we put the library? decode the mojang path + OneSixLibrary lib(libraryName); + lib.finalize(); + + auto cacheentry = MMC->metacache()->resolveEntry("libraries", lib.storagePath()); + finalPath = "libraries/" + lib.storagePath(); + if (!ensureFilePathExists(finalPath)) + return; + + if (!zip.setCurrentFile(internalPath)) + return; + if (!file.open(QIODevice::ReadOnly)) + return; + { + QByteArray data = file.readAll(); + // extract file + QSaveFile extraction(finalPath); + if (!extraction.open(QIODevice::WriteOnly)) + return; + if (extraction.write(data) != data.size()) + return; + if (!extraction.commit()) + return; + QCryptographicHash md5sum(QCryptographicHash::Md5); + md5sum.addData(data); + + cacheentry->stale = false; + cacheentry->md5sum = md5sum.result().toHex().constData(); + MMC->metacache()->updateEntry(cacheentry); + } + file.close(); + + m_forge_version = newVersion; + realVersionId = m_forge_version->id = installObj.value("minecraft").toString(); +} + +bool ForgeInstaller::apply(std::shared_ptr<OneSixVersion> to) +{ + if (!m_forge_version) + return false; + to->externalUpdateStart(); + int sliding_insert_window = 0; + { + // for each library in the version we are adding (except for the blacklisted) + QSet<QString> blacklist{"lwjgl", "lwjgl_util", "lwjgl-platform"}; + for (auto lib : m_forge_version->libraries) + { + QString libName = lib->name(); + // WARNING: This could actually break. + // if this is the actual forge lib, set an absolute url for the download + if (libName.contains("minecraftforge")) + { + lib->setAbsoluteUrl(m_universal_url); + } + else if (libName.contains("scala")) + { + lib->setHint("forge-pack-xz"); + } + if (blacklist.contains(libName)) + continue; + + // find an entry that matches this one + bool found = false; + for (auto tolib : to->libraries) + { + if (tolib->name() != libName) + continue; + found = true; + // replace lib + tolib = lib; + break; + } + if (!found) + { + // add lib + to->libraries.insert(sliding_insert_window, lib); + sliding_insert_window++; + } + } + to->mainClass = m_forge_version->mainClass; + to->minecraftArguments = m_forge_version->minecraftArguments; + to->processArguments = m_forge_version->processArguments; + } + to->externalUpdateFinish(); + return to->toOriginalFile(); +} diff --git a/logic/ForgeInstaller.h b/logic/ForgeInstaller.h new file mode 100644 index 00000000..f6f22a2a --- /dev/null +++ b/logic/ForgeInstaller.h @@ -0,0 +1,25 @@ +#pragma once +#include <QString> +#include <memory> + +class OneSixVersion; + +class ForgeInstaller +{ +public: + ForgeInstaller(QString filename, QString universal_url); + + bool apply(std::shared_ptr<OneSixVersion> to); + +private: + // the version, read from the installer + std::shared_ptr<OneSixVersion> m_forge_version; + QString internalPath; + QString finalPath; + QString realVersionId; + QString m_universal_url; +}; + + + + diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index f0630568..0da62803 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -22,7 +22,7 @@ #include "LegacyInstance.h" #include "OneSixInstance.h" #include "NostalgiaInstance.h" -#include "InstanceVersion.h" +#include "BaseVersion.h" #include "MinecraftVersion.h" #include "inifile.h" @@ -30,6 +30,7 @@ #include <setting.h> #include "pathutils.h" +#include <logger/QsLog.h> InstanceFactory InstanceFactory::loader; @@ -68,16 +69,16 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst } -InstanceFactory::InstCreateError InstanceFactory::createInstance( BaseInstance*& inst, InstVersionPtr version, const QString& instDir ) +InstanceFactory::InstCreateError InstanceFactory::createInstance( BaseInstance*& inst, BaseVersionPtr version, const QString& instDir ) { QDir rootDir(instDir); - qDebug(instDir.toUtf8()); + QLOG_DEBUG() << instDir.toUtf8(); if (!rootDir.exists() && !rootDir.mkpath(".")) { return InstanceFactory::CantCreateDir; } - auto mcVer = version.dynamicCast<MinecraftVersion>(); + auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(version); if(!mcVer) return InstanceFactory::NoSuchVersion; @@ -89,19 +90,19 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance( BaseInstance*& case MinecraftVersion::Legacy: m_settings->set("InstanceType", "Legacy"); inst = new LegacyInstance(instDir, m_settings, this); - inst->setIntendedVersionId(version->descriptor); + inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; case MinecraftVersion::OneSix: m_settings->set("InstanceType", "OneSix"); inst = new OneSixInstance(instDir, m_settings, this); - inst->setIntendedVersionId(version->descriptor); + inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; case MinecraftVersion::Nostalgia: m_settings->set("InstanceType", "Nostalgia"); inst = new NostalgiaInstance(instDir, m_settings, this); - inst->setIntendedVersionId(version->descriptor); + inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; default: diff --git a/logic/InstanceFactory.h b/logic/InstanceFactory.h index ed54f520..1c527749 100644 --- a/logic/InstanceFactory.h +++ b/logic/InstanceFactory.h @@ -19,9 +19,9 @@ #include <QMap> #include <QList> -#include "InstanceVersion.h" +#include "BaseVersion.h" -class InstVersion; +class BaseVersion; class BaseInstance; /*! @@ -61,7 +61,7 @@ public: * - InstExists if the given instance directory is already an instance. * - CantCreateDir if the given instance directory cannot be created. */ - InstCreateError createInstance(BaseInstance *&inst, InstVersionPtr version, const QString &instDir); + InstCreateError createInstance(BaseInstance *&inst, BaseVersionPtr version, const QString &instDir); /*! * \brief Loads an instance from the given directory. diff --git a/logic/InstanceLauncher.cpp b/logic/InstanceLauncher.cpp index 312f4c69..720052a3 100644 --- a/logic/InstanceLauncher.cpp +++ b/logic/InstanceLauncher.cpp @@ -3,9 +3,9 @@ #include <iostream> #include "gui/logindialog.h" -#include "gui/taskdialog.h" +#include "gui/ProgressDialog.h" #include "gui/consolewindow.h" -#include "logic/tasks/LoginTask.h" +#include "logic/net/LoginTask.h" #include "logic/MinecraftProcess.h" #include "lists/InstanceList.h" @@ -25,13 +25,13 @@ void InstanceLauncher::onLoginComplete() LoginTask * task = ( LoginTask * ) QObject::sender(); auto result = task->getResult(); auto instance = MMC->instances()->getInstanceById(instId); - proc = instance->prepareForLaunch ( result.username, result.sessionID ); + proc = instance->prepareForLaunch ( result ); if ( !proc ) { //FIXME: report error return; } - console = new ConsoleWindow(); + console = new ConsoleWindow(proc); console->show(); connect ( proc, SIGNAL ( ended() ), SLOT ( onTerminated() ) ); @@ -48,7 +48,7 @@ void InstanceLauncher::doLogin ( const QString& errorMsg ) { UserInfo uInfo {loginDlg->getUsername(), loginDlg->getPassword() }; - TaskDialog* tDialog = new TaskDialog ( nullptr ); + ProgressDialog* tDialog = new ProgressDialog ( nullptr ); LoginTask* loginTask = new LoginTask ( uInfo, tDialog ); connect ( loginTask, SIGNAL ( succeeded() ),SLOT ( onLoginComplete() ), Qt::QueuedConnection ); connect ( loginTask, SIGNAL ( failed ( QString ) ),SLOT ( doLogin ( QString ) ), Qt::QueuedConnection ); @@ -61,7 +61,7 @@ int InstanceLauncher::launch() { std::cout << "Launching Instance '" << qPrintable ( instId ) << "'" << std::endl; auto instance = MMC->instances()->getInstanceById(instId); - if ( instance.isNull() ) + if ( !instance ) { std::cout << "Could not find instance requested. note that you have to specify the ID, not the NAME" << std::endl; return 1; diff --git a/logic/InstanceVersion.h b/logic/InstanceVersion.h deleted file mode 100644 index eecd9c4e..00000000 --- a/logic/InstanceVersion.h +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include <QSharedPointer> - -/*! - * An abstract base class for versions. - */ -struct InstVersion -{ - /*! - * Checks if this version is less (older) than the given version. - * \param other The version to compare this one to. - * \return True if this version is older than the given version. - */ - virtual bool operator<(const InstVersion &rhs) const - { - return timestamp < rhs.timestamp; - } - - /*! - * Checks if this version is greater (newer) than the given version. - * \param other The version to compare this one to. - * \return True if this version is newer than the given version. - */ - virtual bool operator>( const InstVersion& rhs ) const - { - return timestamp > rhs.timestamp; - } - - /*! - * A string used to identify this version in config files. - * This should be unique within the version list or shenanigans will occur. - */ - QString descriptor; - /*! - * The name of this version as it is displayed to the user. - * For example: "1.5.1" - */ - QString name; - /*! - * Gets the version's timestamp. - * This is primarily used for sorting versions in a list. - */ - qint64 timestamp; - - virtual QString typeString() const - { - return "InstVersion"; - } -}; - -typedef QSharedPointer<InstVersion> InstVersionPtr; - -Q_DECLARE_METATYPE( InstVersionPtr )
\ No newline at end of file diff --git a/logic/JavaUtils.cpp b/logic/JavaUtils.cpp new file mode 100644 index 00000000..8e9c984f --- /dev/null +++ b/logic/JavaUtils.cpp @@ -0,0 +1,183 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "JavaUtils.h" +#include "pathutils.h" +#include "MultiMC.h" + +#include <QStringList> +#include <QString> +#include <QDir> +#include <QMessageBox> +#include <logger/QsLog.h> +#include <gui/versionselectdialog.h> +#include <setting.h> + +JavaUtils::JavaUtils() +{ + +} + +JavaVersionPtr JavaUtils::GetDefaultJava() +{ + JavaVersionPtr javaVersion(new JavaVersion()); + + javaVersion->id = "java"; + javaVersion->arch = "unknown"; + javaVersion->path = "java"; + javaVersion->recommended = false; + + return javaVersion; +} + +#if WINDOWS +QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName) +{ + QList<JavaVersionPtr> javas; + + QString archType = "unknown"; + if(keyType == KEY_WOW64_64KEY) archType = "64"; + else if(keyType == KEY_WOW64_32KEY) archType = "32"; + + HKEY jreKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS) + { + // Read the current type version from the registry. + // This will be used to find any key that contains the JavaHome value. + char *value = new char[0]; + DWORD valueSz = 0; + if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE*)value, &valueSz) == ERROR_MORE_DATA) + { + value = new char[valueSz]; + RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE*)value, &valueSz); + } + + QString recommended = value; + + TCHAR subKeyName[255]; + DWORD subKeyNameSize, numSubKeys, retCode; + + // Get the number of subkeys + RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + // Iterate until RegEnumKeyEx fails + if(numSubKeys > 0) + { + for(int i = 0; i < numSubKeys; i++) + { + subKeyNameSize = 255; + retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL); + if(retCode == ERROR_SUCCESS) + { + // Now open the registry key for the version that we just got. + QString newKeyName = keyName + "\\" + subKeyName; + + HKEY newKey; + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) + { + // Read the JavaHome value to find where Java is installed. + value = new char[0]; + valueSz = 0; + if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE*)value, &valueSz) == ERROR_MORE_DATA) + { + value = new char[valueSz]; + RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE*)value, &valueSz); + + // Now, we construct the version object and add it to the list. + JavaVersionPtr javaVersion(new JavaVersion()); + + javaVersion->id = subKeyName; + javaVersion->arch = archType; + javaVersion->path = QDir(PathCombine(value, "bin")).absoluteFilePath("java.exe"); + javaVersion->recommended = (recommended == subKeyName); + javas.append(javaVersion); + } + + RegCloseKey(newKey); + } + } + } + } + + RegCloseKey(jreKey); + } + + return javas; +} + +QList<JavaVersionPtr> JavaUtils::FindJavaPaths() +{ + QList<JavaVersionPtr> javas; + + QList<JavaVersionPtr> JRE64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); + QList<JavaVersionPtr> JDK64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); + QList<JavaVersionPtr> JRE32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); + QList<JavaVersionPtr> JDK32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); + + javas.append(JRE64s); + javas.append(JDK64s); + javas.append(JRE32s); + javas.append(JDK32s); + + if(javas.size() <= 0) + { + QLOG_WARN() << "Failed to find Java in the Windows registry - defaulting to \"java\""; + javas.append(this->GetDefaultJava()); + return javas; + } + + QLOG_INFO() << "Found the following Java installations (64 -> 32, JRE -> JDK): "; + + for(auto &java : javas) + { + QString sRec; + if(java->recommended) sRec = "(Recommended)"; + QLOG_INFO() << java->id << java->arch << " at " << java->path << sRec; + } + + return javas; +} +#elif OSX +QList<JavaVersionPtr> JavaUtils::FindJavaPaths() +{ + QLOG_INFO() << "OS X Java detection incomplete - defaulting to \"java\""; + + QList<JavaVersionPtr> javas; + javas.append(this->GetDefaultJava()); + + return javas; +} + +#elif LINUX +QList<JavaVersionPtr> JavaUtils::FindJavaPaths() +{ + QLOG_INFO() << "Linux Java detection incomplete - defaulting to \"java\""; + + QList<JavaVersionPtr> javas; + javas.append(this->GetDefaultJava()); + + return javas; +} +#else +QList<JavaVersionPtr> JavaUtils::FindJavaPaths() +{ + QLOG_INFO() << "Unknown operating system build - defaulting to \"java\""; + + QList<JavaVersionPtr> javas; + javas.append(this->GetDefaultJava()); + + return javas; +} +#endif diff --git a/logic/JavaUtils.h b/logic/JavaUtils.h new file mode 100644 index 00000000..e4f777d0 --- /dev/null +++ b/logic/JavaUtils.h @@ -0,0 +1,40 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QStringList> +#include <QWidget> +#include <logic/lists/JavaVersionList.h> +#include "osutils.h" + +#if WINDOWS + #include <windows.h> +#endif + +class JavaUtils +{ +public: + JavaUtils(); + + QList<JavaVersionPtr> FindJavaPaths(); + JavaVersionPtr GetDefaultJava(); + +private: + +#if WINDOWS + QList<JavaVersionPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName); +#endif +}; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 0672d2c8..f741caad 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -14,8 +14,9 @@ #define LAUNCHER_FILE "MultiMCLauncher.jar" -LegacyInstance::LegacyInstance(const QString& rootDir, SettingsObject* settings, QObject* parent) - :BaseInstance( new LegacyInstancePrivate(),rootDir, settings, parent) +LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent) + : BaseInstance(new LegacyInstancePrivate(), rootDir, settings, parent) { settings->registerSetting(new Setting("NeedsRebuild", true)); settings->registerSetting(new Setting("ShouldUpdate", false)); @@ -24,97 +25,96 @@ LegacyInstance::LegacyInstance(const QString& rootDir, SettingsObject* settings, settings->registerSetting(new Setting("IntendedJarVersion", "")); } -BaseUpdate* LegacyInstance::doUpdate() +BaseUpdate *LegacyInstance::doUpdate() { + auto list = jarModList(); return new LegacyUpdate(this, this); } -MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session) +MinecraftProcess *LegacyInstance::prepareForLaunch(LoginResponse response) { - MinecraftProcess * proc = new MinecraftProcess(this); - + MinecraftProcess *proc = new MinecraftProcess(this); + QIcon icon = MMC->icons()->getIcon(iconKey()); - auto pixmap = icon.pixmap(128,128); - pixmap.save(PathCombine(minecraftRoot(), "icon.png"),"PNG"); - + auto pixmap = icon.pixmap(128, 128); + pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); + // extract the legacy launcher QFile(":/launcher/launcher.jar").copy(PathCombine(minecraftRoot(), LAUNCHER_FILE)); - + // set the process arguments { QStringList args; - + // window size QString windowSize; if (settings().get("LaunchMaximized").toBool()) windowSize = "max"; else - windowSize = QString("%1x%2"). - arg(settings().get("MinecraftWinWidth").toInt()). - arg(settings().get("MinecraftWinHeight").toInt()); - + windowSize = QString("%1x%2").arg(settings().get("MinecraftWinWidth").toInt()).arg( + settings().get("MinecraftWinHeight").toInt()); + // window title QString windowTitle; windowTitle.append("MultiMC: ").append(name()); - + // Java arguments args.append(Util::Commandline::splitArgs(settings().get("JvmArgs").toString())); - + #ifdef OSX // OSX dock icon and name args << "-Xdock:icon=icon.png"; args << QString("-Xdock:name=\"%1\"").arg(windowTitle); #endif - - QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion()).absolutePath(); - + + QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion()) + .absolutePath(); + // launcher arguments args << QString("-Xms%1m").arg(settings().get("MinMemAlloc").toInt()); args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt()); args << QString("-XX:PermSize=%1m").arg(settings().get("PermGen").toInt()); args << "-jar" << LAUNCHER_FILE; - args << user; - args << session; + args << response.player_name; + args << response.session_id; args << windowTitle; args << windowSize; args << lwjgl; proc->setMinecraftArguments(args); } - + // set the process work path proc->setMinecraftWorkdir(minecraftRoot()); - + return proc; } void LegacyInstance::cleanupAfterRun() { - //FIXME: delete the launcher and icons and whatnot. + // FIXME: delete the launcher and icons and whatnot. } -QSharedPointer< ModList > LegacyInstance::coreModList() +std::shared_ptr<ModList> LegacyInstance::coreModList() { I_D(LegacyInstance); - if(!d->core_mod_list) + if (!d->core_mod_list) { d->core_mod_list.reset(new ModList(coreModsDir())); } - else - d->core_mod_list->update(); + d->core_mod_list->update(); return d->core_mod_list; } -QSharedPointer< ModList > LegacyInstance::jarModList() +std::shared_ptr<ModList> LegacyInstance::jarModList() { I_D(LegacyInstance); - if(!d->jar_mod_list) + if (!d->jar_mod_list) { auto list = new ModList(jarModsDir(), modListFile()); connect(list, SIGNAL(changed()), SLOT(jarModsChanged())); d->jar_mod_list.reset(list); } - else - d->jar_mod_list->update(); + d->jar_mod_list->update(); return d->jar_mod_list; } @@ -123,38 +123,33 @@ void LegacyInstance::jarModsChanged() setShouldRebuild(true); } - -QSharedPointer< ModList > LegacyInstance::loaderModList() +std::shared_ptr<ModList> LegacyInstance::loaderModList() { I_D(LegacyInstance); - if(!d->loader_mod_list) + if (!d->loader_mod_list) { d->loader_mod_list.reset(new ModList(loaderModsDir())); } - else - d->loader_mod_list->update(); + d->loader_mod_list->update(); return d->loader_mod_list; } -QSharedPointer< ModList > LegacyInstance::texturePackList() +std::shared_ptr<ModList> LegacyInstance::texturePackList() { I_D(LegacyInstance); - if(!d->texture_pack_list) + if (!d->texture_pack_list) { d->texture_pack_list.reset(new ModList(texturePacksDir())); } - else - d->texture_pack_list->update(); + d->texture_pack_list->update(); return d->texture_pack_list; } - -QDialog * LegacyInstance::createModEditDialog ( QWidget* parent ) +QDialog *LegacyInstance::createModEditDialog(QWidget *parent) { return new LegacyModEditDialog(this, parent); } - QString LegacyInstance::jarModsDir() const { return PathCombine(instanceRoot(), "instMods"); @@ -204,7 +199,6 @@ QString LegacyInstance::instanceConfigFolder() const return PathCombine(minecraftRoot(), "config"); } - /* bool LegacyInstance::shouldUpdateCurrentVersion() const { @@ -215,21 +209,22 @@ bool LegacyInstance::shouldUpdateCurrentVersion() const void LegacyInstance::updateCurrentVersion(bool keepCurrent) { QFileInfo jar(runnableJar()); - + if(!jar.exists()) { setLastCurrentVersionUpdate(0); setCurrentVersionId("Unknown"); return; } - + qint64 time = jar.lastModified().toUTC().toMSecsSinceEpoch(); - + setLastCurrentVersionUpdate(time); if (!keepCurrent) { // TODO: Implement GetMinecraftJarVersion function. - QString newVersion = "Unknown";//javautils::GetMinecraftJarVersion(jar.absoluteFilePath()); + QString newVersion = +"Unknown";//javautils::GetMinecraftJarVersion(jar.absoluteFilePath()); setCurrentVersionId(newVersion); } } @@ -247,41 +242,41 @@ void LegacyInstance::setLastCurrentVersionUpdate ( qint64 val ) bool LegacyInstance::shouldRebuild() const { I_D(LegacyInstance); - return d->m_settings->get ( "NeedsRebuild" ).toBool(); + return d->m_settings->get("NeedsRebuild").toBool(); } -void LegacyInstance::setShouldRebuild ( bool val ) +void LegacyInstance::setShouldRebuild(bool val) { I_D(LegacyInstance); - d->m_settings->set ( "NeedsRebuild", val ); + d->m_settings->set("NeedsRebuild", val); } QString LegacyInstance::currentVersionId() const { I_D(LegacyInstance); - return d->m_settings->get ( "JarVersion" ).toString(); + return d->m_settings->get("JarVersion").toString(); } -void LegacyInstance::setCurrentVersionId ( QString val ) +void LegacyInstance::setCurrentVersionId(QString val) { I_D(LegacyInstance); - d->m_settings->set ( "JarVersion", val ); + d->m_settings->set("JarVersion", val); } QString LegacyInstance::lwjglVersion() const { I_D(LegacyInstance); - return d->m_settings->get ( "LwjglVersion" ).toString(); + return d->m_settings->get("LwjglVersion").toString(); } -void LegacyInstance::setLWJGLVersion ( QString val ) +void LegacyInstance::setLWJGLVersion(QString val) { I_D(LegacyInstance); - d->m_settings->set ( "LwjglVersion", val ); + d->m_settings->set("LwjglVersion", val); } QString LegacyInstance::intendedVersionId() const { I_D(LegacyInstance); - return d->m_settings->get ( "IntendedJarVersion" ).toString(); + return d->m_settings->get("IntendedJarVersion").toString(); } -bool LegacyInstance::setIntendedVersionId ( QString version ) +bool LegacyInstance::setIntendedVersionId(QString version) { settings().set("IntendedJarVersion", version); setShouldUpdate(true); @@ -290,16 +285,16 @@ bool LegacyInstance::setIntendedVersionId ( QString version ) bool LegacyInstance::shouldUpdate() const { I_D(LegacyInstance); - QVariant var = settings().get ( "ShouldUpdate" ); - if ( !var.isValid() || var.toBool() == false ) + QVariant var = settings().get("ShouldUpdate"); + if (!var.isValid() || var.toBool() == false) { return intendedVersionId() != currentVersionId(); } return true; } -void LegacyInstance::setShouldUpdate ( bool val ) +void LegacyInstance::setShouldUpdate(bool val) { - settings().set ( "ShouldUpdate", val ); + settings().set("ShouldUpdate", val); } QString LegacyInstance::defaultBaseJar() const @@ -312,14 +307,16 @@ QString LegacyInstance::defaultCustomBaseJar() const return PathCombine(binDir(), "mcbackup.jar"); } -bool LegacyInstance::menuActionEnabled ( QString action_name ) const +bool LegacyInstance::menuActionEnabled(QString action_name) const { + if (action_name == "actionChangeInstMCVersion") + return false; return true; } QString LegacyInstance::getStatusbarDescription() { - if(shouldUpdate()) + if (shouldUpdate()) return "Legacy : " + currentVersionId() + " -> " + intendedVersionId(); else return "Legacy : " + currentVersionId(); diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index b36026fc..8bf334f6 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -9,21 +9,22 @@ class LegacyInstance : public BaseInstance { Q_OBJECT public: - - explicit LegacyInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0); - + + explicit LegacyInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + /// Path to the instance's minecraft.jar QString runnableJar() const; - + //! Path to the instance's modlist file. QString modListFile() const; - + ////// Mod Lists ////// - QSharedPointer<ModList> jarModList(); - QSharedPointer<ModList> coreModList(); - QSharedPointer<ModList> loaderModList(); - QSharedPointer<ModList> texturePackList(); - + std::shared_ptr<ModList> jarModList(); + std::shared_ptr<ModList> coreModList(); + std::shared_ptr<ModList> loaderModList(); + std::shared_ptr<ModList> texturePackList(); + ////// Directories ////// QString savesDir() const; QString texturePacksDir() const; @@ -33,40 +34,47 @@ public: QString coreModsDir() const; QString resourceDir() const; virtual QString instanceConfigFolder() const; - + /*! * Whether or not the instance's minecraft.jar needs to be rebuilt. - * If this is true, when the instance launches, its jar mods will be + * If this is true, when the instance launches, its jar mods will be * re-added to a fresh minecraft.jar file. */ bool shouldRebuild() const; void setShouldRebuild(bool val); - + virtual QString currentVersionId() const; virtual void setCurrentVersionId(QString val); - + //! The version of LWJGL that this instance uses. QString lwjglVersion() const; /// st the version of LWJGL libs this instance will use void setLWJGLVersion(QString val); - + virtual QString intendedVersionId() const; - virtual bool setIntendedVersionId ( QString version ); - + virtual bool setIntendedVersionId(QString version); + // the `version' of Legacy instances is defined by the launcher code. + // in contrast with OneSix, where `version' is described in a json file + virtual bool versionIsCustom() override + { + return false; + }; + virtual bool shouldUpdate() const; virtual void setShouldUpdate(bool val); - virtual BaseUpdate* doUpdate(); - - virtual MinecraftProcess* prepareForLaunch( QString user, QString session ); + virtual BaseUpdate *doUpdate(); + + virtual MinecraftProcess *prepareForLaunch(LoginResponse response); virtual void cleanupAfterRun(); - virtual QDialog * createModEditDialog ( QWidget* parent ); - + virtual QDialog *createModEditDialog(QWidget *parent); + virtual QString defaultBaseJar() const; virtual QString defaultCustomBaseJar() const; - - bool menuActionEnabled ( QString action_name ) const; + + bool menuActionEnabled(QString action_name) const; virtual QString getStatusbarDescription(); - -protected slots: + +protected +slots: virtual void jarModsChanged(); };
\ No newline at end of file diff --git a/logic/LegacyInstance_p.h b/logic/LegacyInstance_p.h index d1f417fe..0809b8d2 100644 --- a/logic/LegacyInstance_p.h +++ b/logic/LegacyInstance_p.h @@ -9,8 +9,8 @@ class ModList; struct LegacyInstancePrivate: public BaseInstancePrivate { - QSharedPointer<ModList> jar_mod_list; - QSharedPointer<ModList> core_mod_list; - QSharedPointer<ModList> loader_mod_list; - QSharedPointer<ModList> texture_pack_list; + std::shared_ptr<ModList> jar_mod_list; + std::shared_ptr<ModList> core_mod_list; + std::shared_ptr<ModList> loader_mod_list; + std::shared_ptr<ModList> texture_pack_list; };
\ No newline at end of file diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index 626ad1e0..66b4bf8a 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -9,9 +9,11 @@ #include <quazip.h> #include <quazipfile.h> #include <JlCompress.h> +#include <logger/QsLog.h> - -LegacyUpdate::LegacyUpdate ( BaseInstance* inst, QObject* parent ) : BaseUpdate ( inst, parent ) {} +LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : BaseUpdate(inst, parent) +{ +} void LegacyUpdate::executeTask() { @@ -20,85 +22,89 @@ void LegacyUpdate::executeTask() void LegacyUpdate::lwjglStart() { - LegacyInstance * inst = (LegacyInstance *) m_inst; + LegacyInstance *inst = (LegacyInstance *)m_inst; + + lwjglVersion = inst->lwjglVersion(); + lwjglTargetPath = PathCombine("lwjgl", lwjglVersion); + lwjglNativesPath = PathCombine(lwjglTargetPath, "natives"); - lwjglVersion = inst->lwjglVersion(); - lwjglTargetPath = PathCombine("lwjgl", lwjglVersion ); - lwjglNativesPath = PathCombine( lwjglTargetPath, "natives"); - // if the 'done' file exists, we don't have to download this again QFileInfo doneFile(PathCombine(lwjglTargetPath, "done")); - if(doneFile.exists()) + if (doneFile.exists()) { jarStart(); return; } - - auto &list = LWJGLVersionList::get(); - if(!list.isLoaded()) + + auto list = MMC->lwjgllist(); + if (!list->isLoaded()) { emitFailed("Too soon! Let the LWJGL list load :)"); return; } - + setStatus("Downloading new LWJGL."); - auto version = list.getVersion(lwjglVersion); - if(!version) + auto version = list->getVersion(lwjglVersion); + if (!version) { emitFailed("Game update failed: the selected LWJGL version is invalid."); return; } - + QString url = version->url(); QUrl realUrl(url); QString hostname = realUrl.host(); auto worker = MMC->qnam(); QNetworkRequest req(realUrl); req.setRawHeader("Host", hostname.toLatin1()); - req.setHeader(QNetworkRequest::UserAgentHeader, "Wget/1.14 (linux-gnu)"); - QNetworkReply * rep = worker->get ( req ); - - m_reply = QSharedPointer<QNetworkReply> (rep, &QObject::deleteLater); - connect(rep, SIGNAL(downloadProgress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); - connect(worker, SIGNAL(finished(QNetworkReply*)), SLOT(lwjglFinished(QNetworkReply*))); - //connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); + QNetworkReply *rep = worker->get(req); + + m_reply = std::shared_ptr<QNetworkReply>(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); + connect(worker.get(), SIGNAL(finished(QNetworkReply *)), + SLOT(lwjglFinished(QNetworkReply *))); + // connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + // SLOT(downloadError(QNetworkReply::NetworkError))); } -void LegacyUpdate::lwjglFinished(QNetworkReply* reply) +void LegacyUpdate::lwjglFinished(QNetworkReply *reply) { - if(m_reply != reply) + if (m_reply.get() != reply) { return; } - if(reply->error() != QNetworkReply::NoError) + if (reply->error() != QNetworkReply::NoError) { - emitFailed( "Failed to download: "+ - reply->errorString()+ - "\nSometimes you have to wait a bit if you download many LWJGL versions in a row. YMMV"); + emitFailed("Failed to download: " + reply->errorString() + + "\nSometimes you have to wait a bit if you download many LWJGL versions in " + "a row. YMMV"); return; } - auto *worker = MMC->qnam(); - //Here i check if there is a cookie for me in the reply and extract it - QList<QNetworkCookie> cookies = qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader)); - if(cookies.count() != 0) + auto worker = MMC->qnam(); + // Here i check if there is a cookie for me in the reply and extract it + QList<QNetworkCookie> cookies = + qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader)); + if (cookies.count() != 0) { - //you must tell which cookie goes with which url + // you must tell which cookie goes with which url worker->cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net")); } - //here you can check for the 302 or whatever other header i need + // here you can check for the 302 or whatever other header i need QVariant newLoc = reply->header(QNetworkRequest::LocationHeader); - if(newLoc.isValid()) + if (newLoc.isValid()) { QString redirectedTo = reply->header(QNetworkRequest::LocationHeader).toString(); QUrl realUrl(redirectedTo); QString hostname = realUrl.host(); QNetworkRequest req(redirectedTo); req.setRawHeader("Host", hostname.toLatin1()); - req.setHeader(QNetworkRequest::UserAgentHeader, "Wget/1.14 (linux-gnu)"); - QNetworkReply * rep = worker->get(req); - connect(rep, SIGNAL(downloadProgress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); - m_reply = QSharedPointer<QNetworkReply> (rep, &QObject::deleteLater); + req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); + QNetworkReply *rep = worker->get(req); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + m_reply = std::shared_ptr<QNetworkReply>(rep); return; } QFile saveMe("lwjgl.zip"); @@ -114,26 +120,26 @@ void LegacyUpdate::extractLwjgl() // make sure the directories are there bool success = ensureFolderPathExists(lwjglNativesPath); - - if(!success) + + if (!success) { emitFailed("Failed to extract the lwjgl libs - error when creating required folders."); return; } - + QuaZip zip("lwjgl.zip"); - if(!zip.open(QuaZip::mdUnzip)) + if (!zip.open(QuaZip::mdUnzip)) { emitFailed("Failed to extract the lwjgl libs - not a valid archive."); return; } - + // and now we are going to access files inside it QuaZipFile file(&zip); - const QString jarNames[] = { "jinput.jar", "lwjgl_util.jar", "lwjgl.jar" }; - for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) + const QString jarNames[] = {"jinput.jar", "lwjgl_util.jar", "lwjgl.jar"}; + for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile()) { - if(!file.open(QIODevice::ReadOnly)) + if (!file.open(QIODevice::ReadOnly)) { zip.close(); emitFailed("Failed to extract the lwjgl libs - error while reading archive."); @@ -141,7 +147,7 @@ void LegacyUpdate::extractLwjgl() } QuaZipFileInfo info; QString name = file.getActualFileName(); - if(name.endsWith('/')) + if (name.endsWith('/')) { file.close(); continue; @@ -156,25 +162,25 @@ void LegacyUpdate::extractLwjgl() } } // Not found? look for the natives - if(destFileName.isEmpty()) + if (destFileName.isEmpty()) { #ifdef Q_OS_WIN32 QString nativesDir = "windows"; #else - #ifdef Q_OS_MAC +#ifdef Q_OS_MAC QString nativesDir = "macosx"; - #else +#else QString nativesDir = "linux"; - #endif +#endif #endif if (name.contains(nativesDir)) { int lastSlash = name.lastIndexOf('/'); - int lastBackSlash = name.lastIndexOf('/'); - if(lastSlash != -1) - name = name.mid(lastSlash+1); - else if(lastBackSlash != -1) - name = name.mid(lastBackSlash+1); + int lastBackSlash = name.lastIndexOf('\\'); + if (lastSlash != -1) + name = name.mid(lastSlash + 1); + else if (lastBackSlash != -1) + name = name.mid(lastBackSlash + 1); destFileName = PathCombine(lwjglNativesPath, name); } } @@ -190,7 +196,7 @@ void LegacyUpdate::extractLwjgl() file.close(); // do not forget to close! } zip.close(); - m_reply.clear(); + m_reply.reset(); QFile doneFile(PathCombine(lwjglTargetPath, "done")); doneFile.open(QIODevice::WriteOnly); doneFile.write("done."); @@ -204,13 +210,13 @@ void LegacyUpdate::lwjglFailed() void LegacyUpdate::jarStart() { - LegacyInstance * inst = (LegacyInstance *) m_inst; - if(!inst->shouldUpdate() || inst->shouldUseCustomBaseJar()) + LegacyInstance *inst = (LegacyInstance *)m_inst; + if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar()) { ModTheJar(); return; } - + setStatus("Checking for jar updates..."); // Make directories QDir binDir(inst->binDir()); @@ -226,11 +232,13 @@ void LegacyUpdate::jarStart() QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/"); QString intended_version_id = inst->intendedVersionId(); urlstr += intended_version_id + "/" + intended_version_id + ".jar"; - - legacyDownloadJob.reset(new DownloadJob(QUrl(urlstr), inst->defaultBaseJar())); - connect(legacyDownloadJob.data(), SIGNAL(finished()), SLOT(jarFinished())); - connect(legacyDownloadJob.data(), SIGNAL(failed()), SLOT(jarFailed())); - connect(legacyDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); + + auto dljob = new DownloadJob("Minecraft.jar for version " + intended_version_id); + dljob->addFileDownload(QUrl(urlstr), inst->defaultBaseJar()); + legacyDownloadJob.reset(dljob); + connect(dljob, SIGNAL(succeeded()), SLOT(jarFinished())); + connect(dljob, SIGNAL(failed()), SLOT(jarFailed())); + connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); legacyDownloadJob->start(); } @@ -246,34 +254,36 @@ void LegacyUpdate::jarFailed() emitFailed("Failed to download the minecraft jar. Try again later."); } -bool LegacyUpdate::MergeZipFiles( QuaZip* into, QFileInfo from, QSet< QString >& contained, MetainfAction metainf ) +bool LegacyUpdate::MergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, + MetainfAction metainf) { setStatus("Installing mods - Adding " + from.fileName()); - + QuaZip modZip(from.filePath()); modZip.open(QuaZip::mdUnzip); - + QuaZipFile fileInsideMod(&modZip); - QuaZipFile zipOutFile( into ); - for(bool more=modZip.goToFirstFile(); more; more=modZip.goToNextFile()) + QuaZipFile zipOutFile(into); + for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) { QString filename = modZip.getCurrentFileName(); - if(filename.contains("META-INF") && metainf == LegacyUpdate::IgnoreMetainf) + if (filename.contains("META-INF") && metainf == LegacyUpdate::IgnoreMetainf) { - qDebug() << "Skipping META-INF " << filename << " from " << from.fileName(); + QLOG_INFO() << "Skipping META-INF " << filename << " from " << from.fileName(); continue; } - if(contained.contains(filename)) + if (contained.contains(filename)) { - qDebug() << "Skipping already contained file " << filename << " from " << from.fileName(); + QLOG_INFO() << "Skipping already contained file " << filename << " from " + << from.fileName(); continue; } contained.insert(filename); - qDebug() << "Adding file " << filename << " from " << from.fileName(); - - if(!fileInsideMod.open(QIODevice::ReadOnly)) + QLOG_INFO() << "Adding file " << filename << " from " << from.fileName(); + + if (!fileInsideMod.open(QIODevice::ReadOnly)) { - qDebug() << "Failed to open " << filename << " from " << from.fileName(); + QLOG_ERROR() << "Failed to open " << filename << " from " << from.fileName(); return false; } /* @@ -284,17 +294,17 @@ bool LegacyUpdate::MergeZipFiles( QuaZip* into, QFileInfo from, QSet< QString >& /* info_out.externalAttr = old_info.externalAttr; */ - if(!zipOutFile.open(QIODevice::WriteOnly, info_out)) + if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) { - qDebug() << "Failed to open " << filename << " in the jar"; + QLOG_ERROR() << "Failed to open " << filename << " in the jar"; fileInsideMod.close(); return false; } - if(!JlCompress::copyData(fileInsideMod, zipOutFile)) + if (!JlCompress::copyData(fileInsideMod, zipOutFile)) { zipOutFile.close(); fileInsideMod.close(); - qDebug() << "Failed to copy data of " << filename << " into the jar"; + QLOG_ERROR() << "Failed to copy data of " << filename << " into the jar"; return false; } zipOutFile.close(); @@ -305,34 +315,34 @@ bool LegacyUpdate::MergeZipFiles( QuaZip* into, QFileInfo from, QSet< QString >& void LegacyUpdate::ModTheJar() { - LegacyInstance * inst = (LegacyInstance *) m_inst; - - if(!inst->shouldRebuild()) + LegacyInstance *inst = (LegacyInstance *)m_inst; + + if (!inst->shouldRebuild()) { emitSucceeded(); return; } - + // Get the mod list auto modList = inst->jarModList(); - - QFileInfo runnableJar (inst->runnableJar()); - QFileInfo baseJar (inst->baseJar()); + + QFileInfo runnableJar(inst->runnableJar()); + QFileInfo baseJar(inst->baseJar()); bool base_is_custom = inst->shouldUseCustomBaseJar(); - + // Nothing to do if there are no jar mods to install, no backup and just the mc jar - if(base_is_custom) + if (base_is_custom) { // yes, this can happen if the instance only has the runnable jar and not the base jar // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe // because that's not something mmc4 guarantees - if(runnableJar.isFile() && !baseJar.exists() && modList->empty()) + if (runnableJar.isFile() && !baseJar.exists() && modList->empty()) { inst->setShouldRebuild(false); emitSucceeded(); return; } - + setStatus("Installing mods - backing up minecraft.jar..."); if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) { @@ -340,24 +350,24 @@ void LegacyUpdate::ModTheJar() return; } } - + if (!baseJar.exists()) { emitFailed("The base jar " + baseJar.filePath() + " does not exist"); return; } - + if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) { emitFailed("Failed to delete old minecraft.jar"); return; } - - //TaskStep(); // STEP 1 + + // TaskStep(); // STEP 1 setStatus("Installing mods - Opening minecraft.jar"); QuaZip zipOut(runnableJar.filePath()); - if(!zipOut.open(QuaZip::mdCreate)) + if (!zipOut.open(QuaZip::mdCreate)) { QFile::remove(runnableJar.filePath()); emitFailed("Failed to open the minecraft.jar for modding"); @@ -374,7 +384,7 @@ void LegacyUpdate::ModTheJar() auto &mod = modList->operator[](i); if (mod.type() == Mod::MOD_ZIPFILE) { - if(!MergeZipFiles(&zipOut, mod.filename(), addedFiles, LegacyUpdate::KeepMetainf)) + if (!MergeZipFiles(&zipOut, mod.filename(), addedFiles, LegacyUpdate::KeepMetainf)) { zipOut.close(); QFile::remove(runnableJar.filePath()); @@ -385,7 +395,8 @@ void LegacyUpdate::ModTheJar() else if (mod.type() == Mod::MOD_SINGLEFILE) { auto filename = mod.filename(); - if(!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) + if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), + filename.fileName())) { zipOut.close(); QFile::remove(runnableJar.filePath()); @@ -393,7 +404,8 @@ void LegacyUpdate::ModTheJar() return; } addedFiles.insert(filename.fileName()); - qDebug() << "Adding file " << filename.fileName() << " from " << filename.absoluteFilePath(); + QLOG_INFO() << "Adding file " << filename.fileName() << " from " + << filename.absoluteFilePath(); } else if (mod.type() == Mod::MOD_FOLDER) { @@ -402,35 +414,36 @@ void LegacyUpdate::ModTheJar() QDir dir(what_to_zip); dir.cdUp(); QString parent_dir = dir.absolutePath(); - if(!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, true, addedFiles)) + if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, true, addedFiles)) { zipOut.close(); QFile::remove(runnableJar.filePath()); emitFailed("Failed to add " + filename.fileName() + " to the jar"); return; } - qDebug() << "Adding folder " << filename.fileName() << " from " << filename.absoluteFilePath(); + QLOG_INFO() << "Adding folder " << filename.fileName() << " from " + << filename.absoluteFilePath(); } } - - if(!MergeZipFiles(&zipOut, baseJar, addedFiles, LegacyUpdate::IgnoreMetainf)) + + if (!MergeZipFiles(&zipOut, baseJar, addedFiles, LegacyUpdate::IgnoreMetainf)) { zipOut.close(); QFile::remove(runnableJar.filePath()); emitFailed("Failed to insert minecraft.jar contents."); return; } - + // Recompress the jar zipOut.close(); - if(zipOut.getZipError()!=0) + if (zipOut.getZipError() != 0) { QFile::remove(runnableJar.filePath()); emitFailed("Failed to finalize minecraft.jar!"); return; - } + } inst->setShouldRebuild(false); - //inst->UpdateVersion(true); + // inst->UpdateVersion(true); emitSucceeded(); return; }
\ No newline at end of file diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h index 05c00495..e84ec56a 100644 --- a/logic/LegacyUpdate.h +++ b/logic/LegacyUpdate.h @@ -56,7 +56,7 @@ private: bool MergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString>& contained, MetainfAction metainf); private: - QSharedPointer<QNetworkReply> m_reply; + std::shared_ptr<QNetworkReply> m_reply; // target version, determined during this task // MinecraftVersion *targetVersion; diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index d34be835..06b7a1f1 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -32,46 +32,45 @@ #define IBUS "@im=ibus" // constructor -MinecraftProcess::MinecraftProcess( BaseInstance* inst ) : - m_instance(inst) +MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst) { - connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finish(int, QProcess::ExitStatus))); - + connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(finish(int, QProcess::ExitStatus))); + // prepare the process environment QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - + #ifdef LINUX // Strip IBus if (env.value("XMODIFIERS").contains(IBUS)) env.insert("XMODIFIERS", env.value("XMODIFIERS").replace(IBUS, "")); #endif - + // export some infos env.insert("INST_NAME", inst->name()); env.insert("INST_ID", inst->id()); env.insert("INST_DIR", QDir(inst->instanceRoot()).absolutePath()); - + this->setProcessEnvironment(env); m_prepostlaunchprocess.setProcessEnvironment(env); - + // std channels connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr())); connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut())); } -void MinecraftProcess::setMinecraftArguments ( QStringList args ) +void MinecraftProcess::setMinecraftArguments(QStringList args) { m_args = args; } -void MinecraftProcess::setMinecraftWorkdir ( QString path ) +void MinecraftProcess::setMinecraftWorkdir(QString path) { QDir mcDir(path); this->setWorkingDirectory(mcDir.absolutePath()); m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); } - // console window void MinecraftProcess::on_stdErr() { @@ -80,18 +79,14 @@ void MinecraftProcess::on_stdErr() m_err_leftover.clear(); QStringList lines = str.split("\n"); bool complete = str.endsWith("\n"); - - for(int i = 0; i < lines.size() - 1; i++) + + for (int i = 0; i < lines.size() - 1; i++) { - QString & line = lines[i]; - MessageLevel::Enum level = MessageLevel::Error; - if(line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || line.contains("[FINEST]") ) - level = MessageLevel::Message; - if(line.contains("[SEVERE]") || line.contains("[WARNING]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - emit log(lines[i].toLocal8Bit(), level); + QString &line = lines[i]; + emit log(line /*.replace(username, "<Username>").replace(sessionID, "<Session ID>")*/, + getLevel(line, MessageLevel::Error)); } - if(!complete) + if (!complete) m_err_leftover = lines.last(); } @@ -102,13 +97,14 @@ void MinecraftProcess::on_stdOut() m_out_leftover.clear(); QStringList lines = str.split("\n"); bool complete = str.endsWith("\n"); - - for(int i = 0; i < lines.size() - 1; i++) + + for (int i = 0; i < lines.size() - 1; i++) { - QString & line = lines[i]; - emit log(lines[i].toLocal8Bit(), MessageLevel::Message); + QString &line = lines[i]; + emit log(line /*.replace(username, "<Username>").replace(sessionID, "<Session ID>")*/, + getLevel(line, MessageLevel::Message)); } - if(!complete) + if (!complete) m_out_leftover = lines.last(); } @@ -117,13 +113,20 @@ void MinecraftProcess::finish(int code, ExitStatus status) { if (status != NormalExit) { - //TODO: error handling + // TODO: error handling } - - emit log("Minecraft exited."); - + + // TODO: Localization + + if (!killed) + //: Message displayed on instance exit + emit log(tr("Minecraft exited with exitcode %1.").arg(status)); + else + //: Message displayed after the instance exits due to kill request + emit log(tr("Minecraft was killed by user."), MessageLevel::Error); + m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); - + // run post-exit if (!m_instance->settings().get("PostExitCommand").toString().isEmpty()) { @@ -131,13 +134,19 @@ void MinecraftProcess::finish(int code, ExitStatus status) m_prepostlaunchprocess.waitForFinished(); if (m_prepostlaunchprocess.exitStatus() != NormalExit) { - //TODO: error handling + // TODO: error handling } } m_instance->cleanupAfterRun(); emit ended(); } +void MinecraftProcess::killMinecraft() +{ + killed = true; + kill(); +} + void MinecraftProcess::launch() { if (!m_instance->settings().get("PreLaunchCommand").toString().isEmpty()) @@ -146,24 +155,42 @@ void MinecraftProcess::launch() m_prepostlaunchprocess.waitForFinished(); if (m_prepostlaunchprocess.exitStatus() != NormalExit) { - //TODO: error handling + // TODO: error handling return; } } - + m_instance->setLastLaunch(); - + emit log(QString("Minecraft folder is: '%1'").arg(workingDirectory())); QString JavaPath = m_instance->settings().get("JavaPath").toString(); emit log(QString("Java path: '%1'").arg(JavaPath)); - emit log(QString("Arguments: '%1'").arg(m_args.join("' '"))); + emit log(QString("Arguments: '%1'").arg( + m_args.join("' '") /*.replace(username, "<Username>").replace(sessionID, "<Session +ID>")*/)); start(JavaPath, m_args); if (!waitForStarted()) { - emit log("Could not launch minecraft!"); + //: Error message displayed if instace can't start + emit log(tr("Could not launch minecraft!")); return; - //TODO: error handling + // TODO: error handling } } +MessageLevel::Enum MinecraftProcess::getLevel(const QString &line, MessageLevel::Enum level) +{ + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || + line.contains("[FINER]") || line.contains("[FINEST]")) + level = MessageLevel::Message; + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + level = MessageLevel::Error; + if (line.contains("[WARNING]")) + level = MessageLevel::Warning; + if (line.contains("Exception in thread") || line.contains(" at ")) + level = MessageLevel::Fatal; + if (line.contains("[DEBUG]")) + level = MessageLevel::Debug; + return level; +}
\ No newline at end of file diff --git a/logic/MinecraftProcess.h b/logic/MinecraftProcess.h index 516bf986..a1dfa23f 100644 --- a/logic/MinecraftProcess.h +++ b/logic/MinecraftProcess.h @@ -59,6 +59,10 @@ public: void setMinecraftArguments(QStringList args); + void killMinecraft(); + + inline void setLogin(QString user, QString sid) { username = user; sessionID = sid; } + signals: /** * @brief emitted when mc has finished and the PostLaunchCommand was run @@ -83,4 +87,9 @@ protected slots: void finish(int, QProcess::ExitStatus status); void on_stdErr(); void on_stdOut(); +private: + bool killed; + MessageLevel::Enum getLevel(const QString &message, MessageLevel::Enum defaultLevel); + QString sessionID; + QString username; }; diff --git a/logic/MinecraftVersion.h b/logic/MinecraftVersion.h index 27977262..53c2f5ef 100644 --- a/logic/MinecraftVersion.h +++ b/logic/MinecraftVersion.h @@ -15,17 +15,16 @@ #pragma once -#include "InstanceVersion.h" +#include "BaseVersion.h" #include <QStringList> -struct MinecraftVersion : public InstVersion +struct MinecraftVersion : public BaseVersion { - // From InstVersion: - /* - QString m_descriptor; - QString m_name; - qint64 m_timestamp; - */ + /*! + * Gets the version's timestamp. + * This is primarily used for sorting versions in a list. + */ + qint64 timestamp; /// The URL that this version will be downloaded from. maybe. QString download_url; @@ -44,6 +43,20 @@ struct MinecraftVersion : public InstVersion /// is this a snapshot? bool is_snapshot = false; + QString m_name; + + QString m_descriptor; + + virtual QString descriptor() + { + return m_descriptor; + } + + virtual QString name() + { + return m_name; + } + virtual QString typeString() const { QStringList pre_final; diff --git a/logic/Mod.cpp b/logic/Mod.cpp index 38faa760..c45e3ad2 100644 --- a/logic/Mod.cpp +++ b/logic/Mod.cpp @@ -1,12 +1,12 @@ -// +// // Copyright 2012 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. @@ -20,26 +20,25 @@ #include <QJsonObject> #include <QJsonArray> #include <QJsonValue> -#include <QDebug> #include <quazip.h> #include <quazipfile.h> #include "Mod.h" #include <pathutils.h> #include <inifile.h> +#include <logger/QsLog.h> - -Mod::Mod( const QFileInfo& file ) +Mod::Mod(const QFileInfo &file) { repath(file); } -void Mod::repath ( const QFileInfo& file ) +void Mod::repath(const QFileInfo &file) { m_file = file; m_name = file.completeBaseName(); m_id = file.fileName(); - + m_type = Mod::MOD_UNKNOWN; if (m_file.isDir()) m_type = MOD_FOLDER; @@ -51,19 +50,19 @@ void Mod::repath ( const QFileInfo& file ) else m_type = MOD_SINGLEFILE; } - if(m_type == MOD_ZIPFILE) + if (m_type == MOD_ZIPFILE) { QuaZip zip(m_file.filePath()); - if(!zip.open(QuaZip::mdUnzip)) + if (!zip.open(QuaZip::mdUnzip)) return; - + QuaZipFile file(&zip); - for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) + for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile()) { QString name = zip.getCurrentFileName(); - if(name == "mcmod.info") + if (name == "mcmod.info") { - if(!file.open(QIODevice::ReadOnly)) + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; @@ -73,9 +72,9 @@ void Mod::repath ( const QFileInfo& file ) zip.close(); return; } - else if(name == "forgeversion.properties") + else if (name == "forgeversion.properties") { - if(!file.open(QIODevice::ReadOnly)) + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; @@ -88,16 +87,16 @@ void Mod::repath ( const QFileInfo& file ) } zip.close(); } - else if(m_type == MOD_FOLDER) + else if (m_type == MOD_FOLDER) { QFileInfo mcmod_info(PathCombine(m_file.filePath(), "mcmod.info")); if (mcmod_info.isFile()) { QFile mcmod(mcmod_info.filePath()); - if(!mcmod.open(QIODevice::ReadOnly)) + if (!mcmod.open(QIODevice::ReadOnly)) return; auto data = mcmod.readAll(); - if(data.isEmpty() || data.isNull()) + if (data.isEmpty() || data.isNull()) return; ReadMCModInfo(data); } @@ -111,35 +110,49 @@ void Mod::repath ( const QFileInfo& file ) // https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc void Mod::ReadMCModInfo(QByteArray contents) { - auto getInfoFromArray = [&]( QJsonArray arr ) -> void + auto getInfoFromArray = [&](QJsonArray arr)->void { - if(!arr.at(0).isObject()) + if (!arr.at(0).isObject()) return; auto firstObj = arr.at(0).toObject(); m_id = firstObj.value("modid").toString(); m_name = firstObj.value("name").toString(); m_version = firstObj.value("version").toString(); + m_homeurl = firstObj.value("url").toString(); + m_description = firstObj.value("description").toString(); + QJsonArray authors = firstObj.value("authors").toArray(); + if(authors.size() == 0) m_authors = ""; + else if(authors.size() >= 1) + { + m_authors = authors.at(0).toString(); + for(int i = 1; i < authors.size(); i++) + { + m_authors += ", " + authors.at(i).toString(); + } + } + m_credits = firstObj.value("credits").toString(); return; - }; + } + ; QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); // this is the very old format that had just the array - if(jsonDoc.isArray()) + if (jsonDoc.isArray()) { getInfoFromArray(jsonDoc.array()); } - else if(jsonDoc.isObject()) + else if (jsonDoc.isObject()) { auto val = jsonDoc.object().value("modinfoversion"); int version = val.toDouble(); - if(version != 2) + if (version != 2) { - qDebug() << "BAD stuff happened to mod json:"; - qDebug() << contents; + QLOG_ERROR() << "BAD stuff happened to mod json:"; + QLOG_ERROR() << contents; return; } auto arrVal = jsonDoc.object().value("modlist"); - if(arrVal.isArray()) + if (arrVal.isArray()) { getInfoFromArray(arrVal.toArray()); } @@ -151,33 +164,34 @@ void Mod::ReadForgeInfo(QByteArray contents) // Read the data m_name = "Minecraft Forge"; m_id = "Forge"; + m_homeurl = "http://www.minecraftforge.net/forum/"; INIFile ini; - if(!ini.loadFile(contents)) + if (!ini.loadFile(contents)) return; - - QString major = ini.get("forge.major.number","0").toString(); - QString minor = ini.get("forge.minor.number","0").toString(); - QString revision = ini.get("forge.revision.number","0").toString(); - QString build = ini.get("forge.build.number","0").toString(); - + + QString major = ini.get("forge.major.number", "0").toString(); + QString minor = ini.get("forge.minor.number", "0").toString(); + QString revision = ini.get("forge.revision.number", "0").toString(); + QString build = ini.get("forge.build.number", "0").toString(); + m_version = major + "." + minor + "." + revision + "." + build; } -bool Mod::replace ( Mod& with ) +bool Mod::replace(Mod &with) { - if(!destroy()) + if (!destroy()) return false; bool success = false; auto t = with.type(); - if(t == MOD_ZIPFILE || t == MOD_SINGLEFILE) + if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE) { success = QFile::copy(with.m_file.filePath(), m_file.path()); } - if(t == MOD_FOLDER) + if (t == MOD_FOLDER) { success = copyPath(with.m_file.filePath(), m_file.path()); } - if(success) + if (success) { m_id = with.m_id; m_mcversion = with.m_mcversion; @@ -190,10 +204,10 @@ bool Mod::replace ( Mod& with ) bool Mod::destroy() { - if(m_type == MOD_FOLDER) + if (m_type == MOD_FOLDER) { QDir d(m_file.filePath()); - if(d.removeRecursively()) + if (d.removeRecursively()) { m_type = MOD_UNKNOWN; return true; @@ -203,7 +217,7 @@ bool Mod::destroy() else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE) { QFile f(m_file.filePath()); - if(f.remove()) + if (f.remove()) { m_type = MOD_UNKNOWN; return true; @@ -213,18 +227,17 @@ bool Mod::destroy() return true; } - QString Mod::version() const { - switch(type()) + switch (type()) { - case MOD_ZIPFILE: - return m_version; - case MOD_FOLDER: - return "Folder"; - case MOD_SINGLEFILE: - return "File"; - default: - return "VOID"; + case MOD_ZIPFILE: + return m_version; + case MOD_FOLDER: + return "Folder"; + case MOD_SINGLEFILE: + return "File"; + default: + return "VOID"; } } diff --git a/logic/Mod.h b/logic/Mod.h index fcfcc139..f3aaf18b 100644 --- a/logic/Mod.h +++ b/logic/Mod.h @@ -1,12 +1,12 @@ -// +// // Copyright 2012 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. @@ -22,46 +22,87 @@ class Mod public: enum ModType { - MOD_UNKNOWN, //!< Indicates an unspecified mod type. - MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. + MOD_UNKNOWN, //!< Indicates an unspecified mod type. + MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). - MOD_FOLDER, //!< The mod is in a folder on the filesystem. + MOD_FOLDER, //!< The mod is in a folder on the filesystem. }; Mod(const QFileInfo &file); - - QFileInfo filename() const { return m_file; } - QString id() const { return m_id; } - ModType type() const { return m_type; } - QString mcversion() const { return m_mcversion; }; - bool valid() {return m_type != MOD_UNKNOWN;} - QString name() const {return m_name; }; - + + QFileInfo filename() const + { + return m_file; + } + QString id() const + { + return m_id; + } + ModType type() const + { + return m_type; + } + QString mcversion() const + { + return m_mcversion; + } + ; + bool valid() + { + return m_type != MOD_UNKNOWN; + } + QString name() const + { + return m_name; + } + QString version() const; - - + + QString homeurl() const + { + return m_homeurl; + } + + QString description() const + { + return m_description; + } + + QString authors() const + { + return m_authors; + } + + QString credits() const + { + return m_credits; + } + // delete all the files of this mod bool destroy(); // replace this mod with a copy of the other - bool replace(Mod & with); + bool replace(Mod &with); // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) void repath(const QFileInfo &file); // WEAK compare operator - used for replacing mods - bool operator ==(const Mod &other) const + bool operator==(const Mod &other) const { return filename() == other.filename(); } bool strongCompare(const Mod &other) const { - return filename() == other.filename() && id() == other.id() && version() == other.version() && type() == other.type(); + return filename() == other.filename() && id() == other.id() && + version() == other.version() && type() == other.type(); } + private: void ReadMCModInfo(QByteArray contents); void ReadForgeInfo(QByteArray contents); + protected: - //FIXME: what do do with those? HMM... + // FIXME: what do do with those? HMM... /* void ReadModInfoData(QString info); void ReadForgeInfoData(QString infoFileData); @@ -72,6 +113,10 @@ protected: QString m_name; QString m_version; QString m_mcversion; + QString m_homeurl; + QString m_description; + QString m_authors; + QString m_credits; ModType m_type; }; diff --git a/logic/ModList.cpp b/logic/ModList.cpp index 84511e4c..236f0db6 100644 --- a/logic/ModList.cpp +++ b/logic/ModList.cpp @@ -1,12 +1,12 @@ -// +// // Copyright 2013 MultiMC Contributors -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,60 +19,60 @@ #include <pathutils.h> #include <QMimeData> #include <QUrl> -#include <QDebug> #include <QUuid> #include <QFileSystemWatcher> +#include <logger/QsLog.h> -ModList::ModList ( const QString& dir, const QString& list_file ) -: QAbstractListModel(), m_dir(dir), m_list_file(list_file) +ModList::ModList(const QString &dir, const QString &list_file) + : QAbstractListModel(), m_dir(dir), m_list_file(list_file) { - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | + QDir::NoSymLinks); m_dir.setSorting(QDir::Name); m_list_id = QUuid::createUuid().toString(); m_watcher = new QFileSystemWatcher(this); is_watching = false; - connect(m_watcher,SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); - update(); + connect(m_watcher, SIGNAL(directoryChanged(QString)), this, + SLOT(directoryChanged(QString))); } void ModList::startWatching() { is_watching = m_watcher->addPath(m_dir.absolutePath()); - if(is_watching) - qDebug() << "Started watching " << m_dir.absolutePath(); + if (is_watching) + QLOG_INFO() << "Started watching " << m_dir.absolutePath(); else - qDebug() << "Failed to start watching " << m_dir.absolutePath(); + QLOG_INFO() << "Failed to start watching " << m_dir.absolutePath(); } void ModList::stopWatching() { is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if(!is_watching) - qDebug() << "Stopped watching " << m_dir.absolutePath(); + if (!is_watching) + QLOG_INFO() << "Stopped watching " << m_dir.absolutePath(); else - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); + QLOG_INFO() << "Failed to stop watching " << m_dir.absolutePath(); } - bool ModList::update() { if (!isValid()) return false; - + QList<Mod> newMods; m_dir.refresh(); auto folderContents = m_dir.entryInfoList(); bool orderWasInvalid = false; - + // first, process the ordered items (if any) int currentOrderIndex = 0; QStringList listOrder = readListFile(); - for(auto item: listOrder) + for (auto item : listOrder) { - QFileInfo info (m_dir.filePath(item)); + QFileInfo info(m_dir.filePath(item)); int idx = folderContents.indexOf(info); // if the file from the index file exists - if(idx != -1) + if (idx != -1) { // remove from the actual folder contents list folderContents.takeAt(idx); @@ -84,26 +84,27 @@ bool ModList::update() orderWasInvalid = true; } } - for(auto entry: folderContents) + for (auto entry : folderContents) { newMods.append(Mod(entry)); } - if(mods.size() != newMods.size()) + if (mods.size() != newMods.size()) { orderWasInvalid = true; } - else for(int i = 0; i < mods.size(); i++) - { - if(!mods[i].strongCompare(newMods[i])) + else + for (int i = 0; i < mods.size(); i++) { - orderWasInvalid = true; - break; + if (!mods[i].strongCompare(newMods[i])) + { + orderWasInvalid = true; + break; + } } - } beginResetModel(); mods.swap(newMods); endResetModel(); - if(orderWasInvalid) + if (orderWasInvalid) { saveListFile(); emit changed(); @@ -111,22 +112,21 @@ bool ModList::update() return true; } -void ModList::directoryChanged ( QString path ) +void ModList::directoryChanged(QString path) { update(); } - QStringList ModList::readListFile() { QStringList stringList; - if(m_list_file.isNull() || m_list_file.isEmpty()) + if (m_list_file.isNull() || m_list_file.isEmpty()) return stringList; - + QFile textFile(m_list_file); - if(!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) + if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) return QStringList(); - + QTextStream textStream(&textFile); while (true) { @@ -144,13 +144,13 @@ QStringList ModList::readListFile() bool ModList::saveListFile() { - if(m_list_file.isNull() || m_list_file.isEmpty()) + if (m_list_file.isNull() || m_list_file.isEmpty()) return false; QFile textFile(m_list_file); - if(!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) return false; QTextStream textStream(&textFile); - for(auto mod:mods) + for (auto mod : mods) { auto pathname = mod.filename(); QString filename = pathname.fileName(); @@ -160,29 +160,28 @@ bool ModList::saveListFile() return false; } - bool ModList::isValid() { return m_dir.exists() && m_dir.isReadable(); } -bool ModList::installMod ( const QFileInfo& filename, int index ) +bool ModList::installMod(const QFileInfo &filename, int index) { - if(!filename.exists() || !filename.isReadable() || index < 0) + if (!filename.exists() || !filename.isReadable() || index < 0) { return false; } Mod m(filename); - if(!m.valid()) + if (!m.valid()) return false; - + // if it's already there, replace the original mod (in place) int idx = mods.indexOf(m); - if(idx != -1) + if (idx != -1) { - if(mods[idx].replace(m)) + if (mods[idx].replace(m)) { - + auto left = this->index(index); auto right = this->index(index, columnCount(QModelIndex()) - 1); emit dataChanged(left, right); @@ -192,33 +191,33 @@ bool ModList::installMod ( const QFileInfo& filename, int index ) } return false; } - + auto type = m.type(); - if(type == Mod::MOD_UNKNOWN) + if (type == Mod::MOD_UNKNOWN) return false; - if(type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE) + if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE) { QString newpath = PathCombine(m_dir.path(), filename.fileName()); - if(!QFile::copy(filename.filePath(), newpath)) + if (!QFile::copy(filename.filePath(), newpath)) return false; m.repath(newpath); beginInsertRows(QModelIndex(), index, index); - mods.insert(index,m); + mods.insert(index, m); endInsertRows(); saveListFile(); emit changed(); return true; } - else if(type == Mod::MOD_FOLDER) + else if (type == Mod::MOD_FOLDER) { - + QString from = filename.filePath(); QString to = PathCombine(m_dir.path(), filename.fileName()); - if(!copyPath(from, to)) + if (!copyPath(from, to)) return false; m.repath(to); beginInsertRows(QModelIndex(), index, index); - mods.insert(index,m); + mods.insert(index, m); endInsertRows(); saveListFile(); emit changed(); @@ -227,12 +226,12 @@ bool ModList::installMod ( const QFileInfo& filename, int index ) return false; } -bool ModList::deleteMod ( int index ) +bool ModList::deleteMod(int index) { - if(index >= mods.size() || index < 0) + if (index >= mods.size() || index < 0) return false; - Mod & m = mods[index]; - if(m.destroy()) + Mod &m = mods[index]; + if (m.destroy()) { beginRemoveRows(QModelIndex(), index, index); mods.removeAt(index); @@ -244,11 +243,11 @@ bool ModList::deleteMod ( int index ) return false; } -bool ModList::deleteMods ( int first, int last ) +bool ModList::deleteMods(int first, int last) { - for(int i = first; i <= last; i++) + for (int i = first; i <= last; i++) { - Mod & m = mods[i]; + Mod &m = mods[i]; m.destroy(); } beginRemoveRows(QModelIndex(), first, last); @@ -259,18 +258,17 @@ bool ModList::deleteMods ( int first, int last ) return true; } - -bool ModList::moveModTo ( int from, int to ) +bool ModList::moveModTo(int from, int to) { - if(from < 0 || from >= mods.size()) + if (from < 0 || from >= mods.size()) return false; if (to >= rowCount()) to = rowCount() - 1; if (to == -1) to = rowCount() - 1; - if(from == to) + if (from == to) return false; - int togap = to > from ? to + 1: to; + int togap = to > from ? to + 1 : to; beginMoveRows(QModelIndex(), from, from, QModelIndex(), togap); mods.move(from, to); endMoveRows(); @@ -279,83 +277,81 @@ bool ModList::moveModTo ( int from, int to ) return true; } -bool ModList::moveModUp ( int from ) +bool ModList::moveModUp(int from) { - if(from > 0) + if (from > 0) return moveModTo(from, from - 1); return false; } -bool ModList::moveModsUp ( int first, int last ) +bool ModList::moveModsUp(int first, int last) { - if(first == 0) + if (first == 0) return false; - + beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1); - mods.move(first-1, last); + mods.move(first - 1, last); endMoveRows(); saveListFile(); emit changed(); return true; } - -bool ModList::moveModDown ( int from ) +bool ModList::moveModDown(int from) { - if(from < 0) + if (from < 0) return false; - if(from < mods.size() - 1) + if (from < mods.size() - 1) return moveModTo(from, from + 1); return false; } -bool ModList::moveModsDown ( int first, int last ) +bool ModList::moveModsDown(int first, int last) { - if(last == mods.size() - 1) + if (last == mods.size() - 1) return false; - + beginMoveRows(QModelIndex(), first, last, QModelIndex(), last + 2); - mods.move(last+1, first); + mods.move(last + 1, first); endMoveRows(); saveListFile(); emit changed(); return true; } - -int ModList::columnCount ( const QModelIndex& parent ) const +int ModList::columnCount(const QModelIndex &parent) const { return 2; } -QVariant ModList::data ( const QModelIndex& index, int role ) const +QVariant ModList::data(const QModelIndex &index, int role) const { - if(!index.isValid()) + if (!index.isValid()) return QVariant(); - + int row = index.row(); int column = index.column(); - - if(row < 0 || row >= mods.size()) + + if (row < 0 || row >= mods.size()) return QVariant(); - - if(role != Qt::DisplayRole) + + if (role != Qt::DisplayRole) return QVariant(); - - switch(column) + + switch (column) { - case 0: - return mods[row].name(); - case 1: - return mods[row].version(); - case 2: - return mods[row].mcversion(); - default: - return QVariant(); + case 0: + return mods[row].name(); + case 1: + return mods[row].version(); + case 2: + return mods[row].mcversion(); + default: + return QVariant(); } } -QVariant ModList::headerData ( int section, Qt::Orientation orientation, int role ) const +QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) return QVariant(); @@ -370,10 +366,9 @@ QVariant ModList::headerData ( int section, Qt::Orientation orientation, int rol } } - -Qt::ItemFlags ModList::flags ( const QModelIndex& index ) const +Qt::ItemFlags ModList::flags(const QModelIndex &index) const { - Qt::ItemFlags defaultFlags = QAbstractListModel::flags ( index ); + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); if (index.isValid()) return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; else @@ -400,78 +395,79 @@ Qt::DropActions ModList::supportedDragActions() const return Qt::MoveAction; } -QMimeData* ModList::mimeData ( const QModelIndexList& indexes ) const +QMimeData *ModList::mimeData(const QModelIndexList &indexes) const { - QMimeData * data = new QMimeData(); - - if(indexes.size() == 0) + QMimeData *data = new QMimeData(); + + if (indexes.size() == 0) return data; - + auto idx = indexes[0]; int row = idx.row(); - if(row <0 || row >= mods.size()) + if (row < 0 || row >= mods.size()) return data; - + QStringList params; params << m_list_id << QString::number(row); data->setText(params.join('|')); return data; } -bool ModList::dropMimeData ( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ) +bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, + const QModelIndex &parent) { if (action == Qt::IgnoreAction) - return true; + return true; // check if the action is supported if (!data || !(action & supportedDropActions())) return false; - if(parent.isValid()) + if (parent.isValid()) { row = parent.row(); column = parent.column(); } - + if (row > rowCount()) row = rowCount(); if (row == -1) row = rowCount(); if (column == -1) column = 0; - qDebug() << "Drop row: " << row << " column: " << column; - + QLOG_INFO() << "Drop row: " << row << " column: " << column; + // files dropped from outside? - if(data->hasUrls()) + if (data->hasUrls()) { bool was_watching = is_watching; - if(was_watching) + if (was_watching) stopWatching(); auto urls = data->urls(); - for(auto url: urls) + for (auto url : urls) { // only local files may be dropped... - if(!url.isLocalFile()) + if (!url.isLocalFile()) continue; QString filename = url.toLocalFile(); installMod(filename, row); - qDebug() << "installing: " << filename; + QLOG_INFO() << "installing: " << filename; } - if(was_watching) + if (was_watching) startWatching(); return true; } - else if(data->hasText()) + else if (data->hasText()) { QString sourcestr = data->text(); auto list = sourcestr.split('|'); - if(list.size() != 2) + if (list.size() != 2) return false; QString remoteId = list[0]; int remoteIndex = list[1].toInt(); - qDebug() << "move: " << sourcestr; + QLOG_INFO() << "move: " << sourcestr; // no moving of things between two lists - if(remoteId != m_list_id) + if (remoteId != m_list_id) return false; // no point moving to the same place... - if(row == remoteIndex) + if (row == remoteIndex) return false; // otherwise, move the mod :D moveModTo(remoteIndex, row); @@ -479,4 +475,3 @@ bool ModList::dropMimeData ( const QMimeData* data, Qt::DropAction action, int r } return false; } - diff --git a/logic/ModList.h b/logic/ModList.h index 5395e9ae..e99b6c82 100644 --- a/logic/ModList.h +++ b/logic/ModList.h @@ -112,3 +112,6 @@ protected: QString m_list_id; QList<Mod> mods; }; + + + diff --git a/logic/NostalgiaInstance.cpp b/logic/NostalgiaInstance.cpp index 039cd9ce..efd8f46b 100644 --- a/logic/NostalgiaInstance.cpp +++ b/logic/NostalgiaInstance.cpp @@ -11,6 +11,10 @@ QString NostalgiaInstance::getStatusbarDescription() return "Nostalgia : " + intendedVersionId(); } +bool NostalgiaInstance::menuActionEnabled(QString action_name) const +{ + return false; +} /* ADD MORE diff --git a/logic/NostalgiaInstance.h b/logic/NostalgiaInstance.h index f2df1828..64eb7a81 100644 --- a/logic/NostalgiaInstance.h +++ b/logic/NostalgiaInstance.h @@ -8,4 +8,6 @@ class NostalgiaInstance : public OneSixInstance public: explicit NostalgiaInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0); virtual QString getStatusbarDescription(); + virtual bool menuActionEnabled(QString action_name) const; }; + diff --git a/logic/OneSixAssets.cpp b/logic/OneSixAssets.cpp index c65ee607..6aa0a207 100644 --- a/logic/OneSixAssets.cpp +++ b/logic/OneSixAssets.cpp @@ -1,8 +1,10 @@ #include <QString> -#include <QDebug> +#include <logger/QsLog.h> #include <QtXml/QtXml> #include "OneSixAssets.h" #include "net/DownloadJob.h" +#include "net/HttpMetaCache.h" +#include "MultiMC.h" inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) { @@ -19,6 +21,7 @@ class ThreadedDeleter : public QThread public: void run() { + QLOG_INFO() << "Cleaning up assets folder..."; QDirIterator iter ( m_base, QDirIterator::Subdirectories ); int base_length = m_base.length(); while ( iter.hasNext() ) @@ -32,12 +35,12 @@ public: trimmedf.remove ( 0, base_length + 1 ); if ( m_whitelist.contains ( trimmedf ) ) { - // qDebug() << trimmedf << " gets to live"; + QLOG_TRACE() << trimmedf << " gets to live"; } else { // DO NOT TOLERATE JUNK - // qDebug() << trimmedf << " dies"; + QLOG_TRACE() << trimmedf << " dies"; QFile f ( filename ); f.remove(); } @@ -65,21 +68,25 @@ void OneSixAssets::fetchXMLFinished() nuke_whitelist.clear(); auto firstJob = index_job->first(); - QByteArray ba = firstJob->m_data; + QByteArray ba = std::dynamic_pointer_cast<ByteArrayDownload>(firstJob)->m_data; QString xmlErrorMsg; QDomDocument doc; if ( !doc.setContent ( ba, false, &xmlErrorMsg ) ) { - qDebug() << "Failed to process s3.amazonaws.com/Minecraft.Resources. XML error:" << xmlErrorMsg << ba; + QLOG_ERROR() << "Failed to process s3.amazonaws.com/Minecraft.Resources. XML error:" << xmlErrorMsg << ba; + emit failed(); + return; } //QRegExp etag_match(".*([a-f0-9]{32}).*"); QDomNodeList contents = doc.elementsByTagName ( "Contents" ); - DownloadJob *job = new DownloadJob(); + DownloadJob *job = new DownloadJob("Assets"); connect ( job, SIGNAL(succeeded()), SLOT(downloadFinished()) ); connect ( job, SIGNAL(failed()), SIGNAL(failed()) ); + auto metacache = MMC->metacache(); + for ( int i = 0; i < contents.length(); i++ ) { QDomElement element = contents.at ( i ).toElement(); @@ -104,22 +111,12 @@ void OneSixAssets::fetchXMLFinished() if ( sizeStr == "0" ) continue; - QString filename = fprefix + keyStr; - QFile check_file ( filename ); - QString client_etag = "nonsense"; - // if there already is a file and md5 checking is in effect and it can be opened - if ( check_file.exists() && check_file.open ( QIODevice::ReadOnly ) ) - { - // check the md5 against the expected one - client_etag = QCryptographicHash::hash ( check_file.readAll(), QCryptographicHash::Md5 ).toHex().constData(); - check_file.close(); - } - - QString trimmedEtag = etagStr.remove ( '"' ); nuke_whitelist.append ( keyStr ); - if(trimmedEtag != client_etag) + + auto entry = metacache->resolveEntry("assets", keyStr, etagStr); + if(entry->stale) { - job->add ( QUrl ( prefix + keyStr ), filename ); + job->addCacheDownload(QUrl(prefix + keyStr), entry); } } if(job->size()) @@ -135,7 +132,8 @@ void OneSixAssets::fetchXMLFinished() } void OneSixAssets::start() { - DownloadJob * job = new DownloadJob(QUrl ( "http://s3.amazonaws.com/Minecraft.Resources/" )); + auto job = new DownloadJob("Assets index"); + job->addByteArrayDownload(QUrl ( "http://s3.amazonaws.com/Minecraft.Resources/" )); connect ( job, SIGNAL(succeeded()), SLOT ( fetchXMLFinished() ) ); index_job.reset ( job ); job->start(); diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index c926df60..d80f6b37 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -2,16 +2,18 @@ #include "OneSixInstance_p.h" #include "OneSixUpdate.h" #include "MinecraftProcess.h" -#include "VersionFactory.h" +#include "OneSixVersion.h" #include <setting.h> #include <pathutils.h> #include <cmdutils.h> #include <JlCompress.h> #include <gui/OneSixModEditDialog.h> +#include <logger/QsLog.h> -OneSixInstance::OneSixInstance ( const QString& rootDir, SettingsObject* setting_obj, QObject* parent ) -: BaseInstance ( new OneSixInstancePrivate(), rootDir, setting_obj, parent ) +OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_obj, + QObject *parent) + : BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent) { I_D(OneSixInstance); d->m_settings->registerSetting(new Setting("IntendedVersion", "")); @@ -19,7 +21,7 @@ OneSixInstance::OneSixInstance ( const QString& rootDir, SettingsObject* setting reloadFullVersion(); } -BaseUpdate* OneSixInstance::doUpdate() +BaseUpdate *OneSixInstance::doUpdate() { return new OneSixUpdate(this); } @@ -34,10 +36,10 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with) int head = 0; while ((head = token_regexp.indexIn(text, head)) != -1) { - result.append(text.mid(tail, head-tail)); + result.append(text.mid(tail, head - tail)); QString key = token_regexp.cap(1); auto iter = with.find(key); - if(iter != with.end()) + if (iter != with.end()) { result.append(*iter); } @@ -48,26 +50,27 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with) return result; } -QStringList OneSixInstance::processMinecraftArgs( QString user, QString session ) +QStringList OneSixInstance::processMinecraftArgs(LoginResponse response) { I_D(OneSixInstance); auto version = d->version; QString args_pattern = version->minecraftArguments; - + QMap<QString, QString> token_mapping; - token_mapping["auth_username"] = user; - token_mapping["auth_session"] = session; - //FIXME: user and player name are DIFFERENT! - token_mapping["auth_player_name"] = user; - //FIXME: WTF is this. I just plugged in a random UUID here. - token_mapping["auth_uuid"] = "7d4bacf0-fd62-11e2-b778-0800200c9a66"; // obviously fake. - - // this is for offline: + // yggdrasil! + token_mapping["auth_username"] = response.username; + token_mapping["auth_session"] = response.session_id; + token_mapping["auth_access_token"] = response.access_token; + token_mapping["auth_player_name"] = response.player_name; + token_mapping["auth_uuid"] = response.player_id; + + // this is for offline?: /* map["auth_player_name"] = "Player"; map["auth_player_name"] = "00000000-0000-0000-0000-000000000000"; */ - + + // these do nothing and are stupid. token_mapping["profile_name"] = name(); token_mapping["version_name"] = version->id; @@ -75,8 +78,8 @@ QStringList OneSixInstance::processMinecraftArgs( QString user, QString session token_mapping["game_directory"] = absRootDir; QString absAssetsDir = QDir("assets/").absolutePath(); token_mapping["game_assets"] = absAssetsDir; - - QStringList parts = args_pattern.split(' ',QString::SkipEmptyParts); + + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); for (int i = 0; i < parts.length(); i++) { parts[i] = replaceTokensIn(parts[i], token_mapping); @@ -84,27 +87,28 @@ QStringList OneSixInstance::processMinecraftArgs( QString user, QString session return parts; } -MinecraftProcess* OneSixInstance::prepareForLaunch ( QString user, QString session ) +MinecraftProcess *OneSixInstance::prepareForLaunch(LoginResponse response) { I_D(OneSixInstance); cleanupAfterRun(); auto version = d->version; - if(!version) + if (!version) return nullptr; auto libs_to_extract = version->getActiveNativeLibs(); QString natives_dir_raw = PathCombine(instanceRoot(), "natives/"); bool success = ensureFolderPathExists(natives_dir_raw); - if(!success) + if (!success) { // FIXME: handle errors return nullptr; } - - for(auto lib: libs_to_extract) + + for (auto lib : libs_to_extract) { QString path = "libraries/" + lib->storagePath(); - qDebug() << "Will extract " << path.toLocal8Bit(); - if(JlCompress::extractWithExceptions(path, natives_dir_raw, lib->extract_excludes).isEmpty()) + QLOG_INFO() << "Will extract " << path.toLocal8Bit(); + if (JlCompress::extractWithExceptions(path, natives_dir_raw, lib->extract_excludes) + .isEmpty()) { return nullptr; } @@ -116,11 +120,11 @@ MinecraftProcess* OneSixInstance::prepareForLaunch ( QString user, QString sessi args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt()); args << QString("-XX:PermSize=%1m").arg(settings().get("PermGen").toInt()); QDir natives_dir(natives_dir_raw); - args << QString("-Djava.library.path=%1").arg( natives_dir.absolutePath() ); + args << QString("-Djava.library.path=%1").arg(natives_dir.absolutePath()); QString classPath; { auto libs = version->getActiveNormalLibs(); - for (auto lib: libs) + for (auto lib : libs) { QFileInfo fi(QString("libraries/") + lib->storagePath()); classPath.append(fi.absoluteFilePath()); @@ -134,16 +138,29 @@ MinecraftProcess* OneSixInstance::prepareForLaunch ( QString user, QString sessi QFileInfo fi(targetstr); classPath.append(fi.absoluteFilePath()); } - if(classPath.size()) + if (classPath.size()) { args << "-cp"; args << classPath; } args << version->mainClass; - args.append(processMinecraftArgs(user, session)); - + args.append(processMinecraftArgs(response)); + + // Set the width and height for 1.6 instances + bool maximize = settings().get("LaunchMaximized").toBool(); + if(maximize) + { + // this is probably a BAD idea + // args << QString("--fullscreen"); + } + else + { + args << QString("--width") << settings().get("MinecraftWinWidth").toString(); + args << QString("--height") << settings().get("MinecraftWinHeight").toString(); + } + // create the process and set its parameters - MinecraftProcess * proc = new MinecraftProcess(this); + MinecraftProcess *proc = new MinecraftProcess(this); proc->setMinecraftArguments(args); proc->setMinecraftWorkdir(minecraftRoot()); return proc; @@ -156,40 +173,42 @@ void OneSixInstance::cleanupAfterRun() dir.removeRecursively(); } -QSharedPointer< ModList > OneSixInstance::loaderModList() +std::shared_ptr<ModList> OneSixInstance::loaderModList() { I_D(OneSixInstance); - if(!d->loader_mod_list) + if (!d->loader_mod_list) { d->loader_mod_list.reset(new ModList(loaderModsDir())); } - else - d->loader_mod_list->update(); + d->loader_mod_list->update(); return d->loader_mod_list; } -QSharedPointer< ModList > OneSixInstance::resourcePackList() +std::shared_ptr<ModList> OneSixInstance::resourcePackList() { I_D(OneSixInstance); - if(!d->resource_pack_list) + if (!d->resource_pack_list) { d->resource_pack_list.reset(new ModList(resourcePacksDir())); } - else - d->resource_pack_list->update(); + d->resource_pack_list->update(); return d->resource_pack_list; } - -QDialog * OneSixInstance::createModEditDialog ( QWidget* parent ) +QDialog *OneSixInstance::createModEditDialog(QWidget *parent) { return new OneSixModEditDialog(this, parent); } -bool OneSixInstance::setIntendedVersionId ( QString version ) +bool OneSixInstance::setIntendedVersionId(QString version) { settings().set("IntendedVersion", version); setShouldUpdate(true); + auto pathCustom = PathCombine(instanceRoot(), "custom.json"); + auto pathOrig = PathCombine(instanceRoot(), "version.json"); + QFile::remove(pathCustom); + QFile::remove(pathOrig); + reloadFullVersion(); return true; } @@ -198,48 +217,85 @@ QString OneSixInstance::intendedVersionId() const return settings().get("IntendedVersion").toString(); } -void OneSixInstance::setShouldUpdate ( bool val ) +void OneSixInstance::setShouldUpdate(bool val) { - settings().set ( "ShouldUpdate", val ); + settings().set("ShouldUpdate", val); } bool OneSixInstance::shouldUpdate() const { I_D(OneSixInstance); - QVariant var = settings().get ( "ShouldUpdate" ); - if ( !var.isValid() || var.toBool() == false ) + QVariant var = settings().get("ShouldUpdate"); + if (!var.isValid() || var.toBool() == false) { return intendedVersionId() != currentVersionId(); } return true; } +bool OneSixInstance::versionIsCustom() +{ + QString verpath_custom = PathCombine(instanceRoot(), "custom.json"); + QFile versionfile(verpath_custom); + return versionfile.exists(); +} + QString OneSixInstance::currentVersionId() const { return intendedVersionId(); } +bool OneSixInstance::customizeVersion() +{ + if (!versionIsCustom()) + { + auto pathCustom = PathCombine(instanceRoot(), "custom.json"); + auto pathOrig = PathCombine(instanceRoot(), "version.json"); + QFile::copy(pathOrig, pathCustom); + return reloadFullVersion(); + } + else + return true; +} + +bool OneSixInstance::revertCustomVersion() +{ + if (versionIsCustom()) + { + auto path = PathCombine(instanceRoot(), "custom.json"); + QFile::remove(path); + return reloadFullVersion(); + } + else + return true; +} + bool OneSixInstance::reloadFullVersion() { I_D(OneSixInstance); - + QString verpath = PathCombine(instanceRoot(), "version.json"); - QFile versionfile(verpath); - if(versionfile.exists() && versionfile.open(QIODevice::ReadOnly)) { - FullVersionFactory fvf; - auto version = fvf.parse(versionfile.readAll()); - versionfile.close(); - if(version) - { - d->version = version; - return true; - } - }; - return false; + QString verpath_custom = PathCombine(instanceRoot(), "custom.json"); + QFile versionfile(verpath_custom); + if (versionfile.exists()) + verpath = verpath_custom; + } + + auto version = OneSixVersion::fromFile(verpath); + if (version) + { + d->version = version; + return true; + } + else + { + d->version.reset(); + return false; + } } -QSharedPointer< OneSixVersion > OneSixInstance::getFullVersion() +std::shared_ptr<OneSixVersion> OneSixInstance::getFullVersion() { I_D(OneSixInstance); return d->version; @@ -255,16 +311,21 @@ QString OneSixInstance::defaultCustomBaseJar() const return PathCombine(instanceRoot(), "custom.jar"); } -bool OneSixInstance::menuActionEnabled ( QString action_name ) const +bool OneSixInstance::menuActionEnabled(QString action_name) const { - if(action_name == "actionChangeInstLWJGLVersion") + if (action_name == "actionChangeInstLWJGLVersion") return false; return true; } QString OneSixInstance::getStatusbarDescription() { - return "One Six : " + intendedVersionId(); + QString descr = "One Six : " + intendedVersionId(); + if (versionIsCustom()) + { + descr + " (custom)"; + } + return descr; } QString OneSixInstance::loaderModsDir() const @@ -281,3 +342,4 @@ QString OneSixInstance::instanceConfigFolder() const { return PathCombine(minecraftRoot(), "config"); } + diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index a4c67ed1..d2276afc 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -10,43 +10,50 @@ class OneSixInstance : public BaseInstance { Q_OBJECT public: - explicit OneSixInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0); - - + explicit OneSixInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + ////// Mod Lists ////// - QSharedPointer<ModList> loaderModList(); - QSharedPointer<ModList> resourcePackList(); - + std::shared_ptr<ModList> loaderModList(); + std::shared_ptr<ModList> resourcePackList(); + ////// Directories ////// QString resourcePacksDir() const; QString loaderModsDir() const; virtual QString instanceConfigFolder() const; - - virtual BaseUpdate* doUpdate(); - virtual MinecraftProcess* prepareForLaunch ( QString user, QString session ); + + virtual BaseUpdate *doUpdate(); + virtual MinecraftProcess *prepareForLaunch(LoginResponse response); virtual void cleanupAfterRun(); - + virtual QString intendedVersionId() const; - virtual bool setIntendedVersionId ( QString version ); - + virtual bool setIntendedVersionId(QString version); + virtual QString currentVersionId() const; // virtual void setCurrentVersionId ( QString val ) {}; - + virtual bool shouldUpdate() const; virtual void setShouldUpdate(bool val); - - virtual QDialog * createModEditDialog ( QWidget* parent ); - + + virtual QDialog *createModEditDialog(QWidget *parent); + /// reload the full version json file. return true on success! bool reloadFullVersion(); /// get the current full version info - QSharedPointer<OneSixVersion> getFullVersion(); - + std::shared_ptr<OneSixVersion> getFullVersion(); + /// revert the current custom version back to base + bool revertCustomVersion(); + /// customize the current base version + bool customizeVersion(); + /// is the current version original, or custom? + virtual bool versionIsCustom() override; + virtual QString defaultBaseJar() const; virtual QString defaultCustomBaseJar() const; - - virtual bool menuActionEnabled ( QString action_name ) const; + + virtual bool menuActionEnabled(QString action_name) const; virtual QString getStatusbarDescription(); + private: - QStringList processMinecraftArgs( QString user, QString session ); + QStringList processMinecraftArgs(LoginResponse response); };
\ No newline at end of file diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h index c098c9e2..06737b6f 100644 --- a/logic/OneSixInstance_p.h +++ b/logic/OneSixInstance_p.h @@ -2,11 +2,12 @@ #include "BaseInstance_p.h" #include "OneSixVersion.h" +#include "OneSixLibrary.h" #include "ModList.h" struct OneSixInstancePrivate: public BaseInstancePrivate { - QSharedPointer<OneSixVersion> version; - QSharedPointer<ModList> loader_mod_list; - QSharedPointer<ModList> resource_pack_list; + std::shared_ptr<OneSixVersion> version; + std::shared_ptr<ModList> loader_mod_list; + std::shared_ptr<ModList> resource_pack_list; };
\ No newline at end of file diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp new file mode 100644 index 00000000..9c1caaa7 --- /dev/null +++ b/logic/OneSixLibrary.cpp @@ -0,0 +1,162 @@ +#include "OneSixLibrary.h" +#include "OneSixRule.h" +#include "OpSys.h" +#include <QJsonArray> +void OneSixLibrary::finalize() +{ + QStringList parts = m_name.split(':'); + QString relative = parts[0]; + relative.replace('.', '/'); + relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; + + if (!m_is_native) + relative += ".jar"; + else + { + if (m_native_suffixes.contains(currentSystem)) + { + relative += "-" + m_native_suffixes[currentSystem] + ".jar"; + } + else + { + // really, bad. + relative += ".jar"; + } + } + + m_decentname = parts[1]; + m_decentversion = parts[2]; + m_storage_path = relative; + m_download_url = m_base_url + relative; + + if (m_rules.empty()) + { + m_is_active = true; + } + else + { + RuleAction result = Disallow; + for (auto rule : m_rules) + { + RuleAction temp = rule->apply(this); + if (temp != Defer) + result = temp; + } + m_is_active = (result == Allow); + } + if (m_is_native) + { + m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); + m_decenttype = "Native"; + } + else + { + m_decenttype = "Java"; + } +} + +void OneSixLibrary::setName(QString name) +{ + m_name = name; +} +void OneSixLibrary::setBaseUrl(QString base_url) +{ + m_base_url = base_url; +} +void OneSixLibrary::setIsNative() +{ + m_is_native = true; +} +void OneSixLibrary::addNative(OpSys os, QString suffix) +{ + m_is_native = true; + m_native_suffixes[os] = suffix; +} +void OneSixLibrary::setRules(QList<std::shared_ptr<Rule>> rules) +{ + m_rules = rules; +} +bool OneSixLibrary::isActive() +{ + return m_is_active; +} +bool OneSixLibrary::isNative() +{ + return m_is_native; +} +QString OneSixLibrary::downloadUrl() +{ + if (m_absolute_url.size()) + return m_absolute_url; + return m_download_url; +} +QString OneSixLibrary::storagePath() +{ + return m_storage_path; +} + +void OneSixLibrary::setAbsoluteUrl(QString absolute_url) +{ + m_absolute_url = absolute_url; +} + +QString OneSixLibrary::absoluteUrl() +{ + return m_absolute_url; +} + +void OneSixLibrary::setHint(QString hint) +{ + m_hint = hint; +} + +QString OneSixLibrary::hint() +{ + return m_hint; +} + +QJsonObject OneSixLibrary::toJson() +{ + QJsonObject libRoot; + libRoot.insert("name", m_name); + if (m_absolute_url.size()) + libRoot.insert("MMC-absoluteUrl", m_absolute_url); + if (m_hint.size()) + libRoot.insert("MMC-hint", m_hint); + if (m_base_url != "http://s3.amazonaws.com/Minecraft.Download/libraries/" && + m_base_url != "https://s3.amazonaws.com/Minecraft.Download/libraries/") + libRoot.insert("url", m_base_url); + if (isNative() && m_native_suffixes.size()) + { + QJsonObject nativeList; + auto iter = m_native_suffixes.begin(); + while (iter != m_native_suffixes.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + } + if (isNative() && extract_excludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : extract_excludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + if (m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + return libRoot; +} diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h new file mode 100644 index 00000000..5e58ef89 --- /dev/null +++ b/logic/OneSixLibrary.h @@ -0,0 +1,104 @@ +#pragma once +#include <QString> +#include <QStringList> +#include <QMap> +#include <memory> +#include <QJsonObject> +#include "OpSys.h" + +class Rule; + +class OneSixLibrary +{ +private: + // basic values used internally (so far) + QString m_name; + QString m_base_url = "http://s3.amazonaws.com/Minecraft.Download/libraries/"; + QList<std::shared_ptr<Rule> > m_rules; + + // custom values + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; + /// download hint - how to actually get the library + QString m_hint; + + // derived values used for real things + /// a decent name fit for display + QString m_decentname; + /// a decent version fit for display + QString m_decentversion; + /// a decent type fit for display + QString m_decenttype; + /// where to store the lib locally + QString m_storage_path; + /// where to download the lib from + QString m_download_url; + /// is this lib actually active on the current OS? + bool m_is_active = false; + /// is the library a native? + bool m_is_native = false; + /// native suffixes per OS + QMap<OpSys, QString> m_native_suffixes; +public: + QStringList extract_excludes; + +public: + /// Constructor + OneSixLibrary(QString name) + { + m_name = name; + } + + QJsonObject toJson(); + + /** + * finalize the library, processing the input values into derived values and state + * + * This SHALL be called after all the values are parsed or after any further change. + */ + void finalize(); + + /// Set the library composite name + void setName(QString name); + /// get a decent-looking name + QString name() + { + return m_decentname; + } + /// get a decent-looking version + QString version() + { + return m_decentversion; + } + /// what kind of library is it? (for display) + QString type() + { + return m_decenttype; + } + /// Set the url base for downloads + void setBaseUrl(QString base_url); + + /// Call this to mark the library as 'native' (it's a zip archive with DLLs) + void setIsNative(); + /// Attach a name suffix to the specified OS native + void addNative(OpSys os, QString suffix); + /// Set the load rules + void setRules(QList<std::shared_ptr<Rule> > rules); + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive(); + /// Returns true if the library is native + bool isNative(); + /// Get the URL to download the library from + QString downloadUrl(); + /// Get the relative path where the library should be saved + QString storagePath(); + + /// set an absolute URL for the library. This is an MMC extension. + void setAbsoluteUrl(QString absolute_url); + QString absoluteUrl(); + + /// set a hint about how to treat the library. This is an MMC extension. + void setHint(QString hint); + QString hint(); +}; diff --git a/logic/OneSixRule.cpp b/logic/OneSixRule.cpp new file mode 100644 index 00000000..cb64c9ba --- /dev/null +++ b/logic/OneSixRule.cpp @@ -0,0 +1,72 @@ +#include "OneSixRule.h" +#include <QJsonObject> +#include <QJsonArray> + +QList<std::shared_ptr<Rule>> rulesFromJsonV4(QJsonObject &objectWithRules) +{ + QList<std::shared_ptr<Rule>> rules; + auto rulesVal = objectWithRules.value("rules"); + if (!rulesVal.isArray()) + return rules; + + QJsonArray ruleList = rulesVal.toArray(); + for (auto ruleVal : ruleList) + { + std::shared_ptr<Rule> rule; + if (!ruleVal.isObject()) + continue; + auto ruleObj = ruleVal.toObject(); + auto actionVal = ruleObj.value("action"); + if (!actionVal.isString()) + continue; + auto action = RuleAction_fromString(actionVal.toString()); + if (action == Defer) + continue; + + auto osVal = ruleObj.value("os"); + if (!osVal.isObject()) + { + // add a new implicit action rule + rules.append(ImplicitRule::create(action)); + continue; + } + + auto osObj = osVal.toObject(); + auto osNameVal = osObj.value("name"); + if (!osNameVal.isString()) + continue; + OpSys requiredOs = OpSys_fromString(osNameVal.toString()); + QString versionRegex = osObj.value("version").toString(); + // add a new OS rule + rules.append(OsRule::create(action, requiredOs, versionRegex)); + } +} + +QJsonObject ImplicitRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + return ruleObj; +} + +QJsonObject OsRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + QJsonObject osObj; + { + osObj.insert("name", OpSys_toString(m_system)); + osObj.insert("version", m_version_regexp); + } + ruleObj.insert("os", osObj); + return ruleObj; +} + +RuleAction RuleAction_fromString(QString name) +{ + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; +}
\ No newline at end of file diff --git a/logic/OneSixRule.h b/logic/OneSixRule.h new file mode 100644 index 00000000..6be01f1b --- /dev/null +++ b/logic/OneSixRule.h @@ -0,0 +1,72 @@ +#pragma once +#include <QString> +#include <QSharedPointer> +#include "OneSixLibrary.h" + +enum RuleAction +{ + Allow, + Disallow, + Defer +}; + +RuleAction RuleAction_fromString(QString); +QList<std::shared_ptr<Rule>> rulesFromJsonV4(QJsonObject &objectWithRules); + +class Rule +{ +protected: + RuleAction m_result; + virtual bool applies(OneSixLibrary * parent) = 0; +public: + Rule(RuleAction result) + :m_result(result) {} + virtual ~Rule(){}; + virtual QJsonObject toJson() = 0; + RuleAction apply(OneSixLibrary * parent) + { + if(applies(parent)) + return m_result; + else + return Defer; + }; +}; + +class OsRule : public Rule +{ +private: + // the OS + OpSys m_system; + // the OS version regexp + QString m_version_regexp; +protected: + virtual bool applies ( OneSixLibrary* ) + { + return (m_system == currentSystem); + } + OsRule(RuleAction result, OpSys system, QString version_regexp) + : Rule(result), m_system(system), m_version_regexp(version_regexp) {} +public: + virtual QJsonObject toJson(); + static std::shared_ptr<OsRule> create(RuleAction result, OpSys system, QString version_regexp) + { + return std::shared_ptr<OsRule> (new OsRule(result, system, version_regexp)); + } +}; + +class ImplicitRule : public Rule +{ +protected: + virtual bool applies ( OneSixLibrary* ) + { + return true; + } + ImplicitRule(RuleAction result) + : Rule(result) {} +public: + virtual QJsonObject toJson(); + static std::shared_ptr<ImplicitRule> create(RuleAction result) + { + return std::shared_ptr<ImplicitRule> (new ImplicitRule(result)); + } +}; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index 428d6ef7..b5f1d78b 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -3,7 +3,7 @@ * 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 @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +#include "MultiMC.h" #include "OneSixUpdate.h" #include <QtNetwork> @@ -22,23 +22,22 @@ #include <QTextStream> #include <QDataStream> -#include <QDebug> - #include "BaseInstance.h" #include "lists/MinecraftVersionList.h" -#include "VersionFactory.h" #include "OneSixVersion.h" +#include "OneSixLibrary.h" #include "OneSixInstance.h" #include "pathutils.h" - -OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent):BaseUpdate(inst, parent){} +OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent) : BaseUpdate(inst, parent) +{ +} void OneSixUpdate::executeTask() { QString intendedVersion = m_inst->intendedVersionId(); - + // Make directories QDir mcDir(m_inst->minecraftRoot()); if (!mcDir.exists() && !mcDir.mkpath(".")) @@ -46,17 +45,18 @@ void OneSixUpdate::executeTask() emitFailed("Failed to create bin folder."); return; } - + // Get a pointer to the version object that corresponds to the instance's version. - targetVersion = MinecraftVersionList::getMainList().findVersion(intendedVersion).dynamicCast<MinecraftVersion>(); - if(targetVersion == nullptr) + targetVersion = std::dynamic_pointer_cast<MinecraftVersion>( + MMC->minecraftlist()->findVersion(intendedVersion)); + if (targetVersion == nullptr) { // don't do anything if it was invalid emitSucceeded(); return; } - - if(m_inst->shouldUpdate()) + + if (m_inst->shouldUpdate()) { versionFileStart(); } @@ -69,45 +69,64 @@ void OneSixUpdate::executeTask() void OneSixUpdate::versionFileStart() { setStatus("Getting the version files from Mojang."); - + QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/"); - urlstr += targetVersion->descriptor + "/" + targetVersion->descriptor + ".json"; - specificVersionDownloadJob.reset(new DownloadJob(QUrl(urlstr))); - connect(specificVersionDownloadJob.data(), SIGNAL(succeeded()), SLOT(versionFileFinished())); - connect(specificVersionDownloadJob.data(), SIGNAL(failed()), SLOT(versionFileFailed())); - connect(specificVersionDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); + urlstr += targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; + auto job = new DownloadJob("Version index"); + job->addByteArrayDownload(QUrl(urlstr)); + specificVersionDownloadJob.reset(job); + connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(versionFileFinished())); + connect(specificVersionDownloadJob.get(), SIGNAL(failed()), SLOT(versionFileFailed())); + connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); specificVersionDownloadJob->start(); } void OneSixUpdate::versionFileFinished() { DownloadPtr DlJob = specificVersionDownloadJob->first(); - - QString version_id = targetVersion->descriptor; + OneSixInstance *inst = (OneSixInstance *)m_inst; + + QString version_id = targetVersion->descriptor(); QString inst_dir = m_inst->instanceRoot(); // save the version file in $instanceId/version.json { - QString version1 = PathCombine(inst_dir, "/version.json"); + QString version1 = PathCombine(inst_dir, "/version.json"); ensureFilePathExists(version1); // FIXME: detect errors here, download to a temp file, swap - QFile vfile1 (version1); - vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly ); - vfile1.write(DlJob->m_data); - vfile1.close(); + QSaveFile vfile1(version1); + if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) + { + emitFailed("Can't open " + version1 + " for writing."); + return; + } + auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data; + qint64 actual = 0; + if ((actual = vfile1.write(data)) != data.size()) + { + emitFailed("Failed to write into " + version1 + ". Written " + actual + " out of " + + data.size() + '.'); + return; + } + if (!vfile1.commit()) + { + emitFailed("Can't commit changes to " + version1); + return; + } } - + // the version is downloaded safely. update is 'done' at this point m_inst->setShouldUpdate(false); - // save the version file in versions/$version/$version.json - /* - //QString version2 = QString("versions/") + version_id + "/" + version_id + ".json"; - //ensurePathExists(version2); - //QFile vfile2 (version2); - //vfile2.open(QIODevice::Truncate | QIODevice::WriteOnly ); - //vfile2.write(DlJob->m_data); - //vfile2.close(); - */ - + + // delete any custom version inside the instance (it's no longer relevant, we did an update) + QString custom = PathCombine(inst_dir, "/custom.json"); + QFile finfo(custom); + if (finfo.exists()) + { + finfo.remove(); + } + inst->reloadFullVersion(); + jarlibStart(); } @@ -118,36 +137,49 @@ void OneSixUpdate::versionFileFailed() void OneSixUpdate::jarlibStart() { - OneSixInstance * inst = (OneSixInstance *) m_inst; + OneSixInstance *inst = (OneSixInstance *)m_inst; bool successful = inst->reloadFullVersion(); - if(!successful) + if (!successful) { - emitFailed("Failed to load the version description file (version.json). It might be corrupted, missing or simply too new."); + emitFailed("Failed to load the version description file (version.json). It might be " + "corrupted, missing or simply too new."); return; } - - QSharedPointer<OneSixVersion> version = inst->getFullVersion(); - + + std::shared_ptr<OneSixVersion> version = inst->getFullVersion(); + // download the right jar, save it in versions/$version/$version.jar QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/"); urlstr += version->id + "/" + version->id + ".jar"; - QString targetstr ("versions/"); + QString targetstr("versions/"); targetstr += version->id + "/" + version->id + ".jar"; - - jarlibDownloadJob.reset(new DownloadJob(QUrl(urlstr), targetstr)); - + + auto job = new DownloadJob("Libraries for instance " + inst->name()); + job->addFileDownload(QUrl(urlstr), targetstr); + jarlibDownloadJob.reset(job); + auto libs = version->getActiveNativeLibs(); libs.append(version->getActiveNormalLibs()); - - for(auto lib: libs) + + auto metacache = MMC->metacache(); + for (auto lib : libs) { - QString download_path = lib->downloadPath(); - QString storage_path = "libraries/" + lib->storagePath(); - jarlibDownloadJob->add(download_path, storage_path); + if (lib->hint() == "local") + continue; + QString download_path = lib->downloadUrl(); + auto entry = metacache->resolveEntry("libraries", lib->storagePath()); + if (entry->stale) + { + if (lib->hint() == "forge-pack-xz") + jarlibDownloadJob->addForgeXzDownload(download_path, entry); + else + jarlibDownloadJob->addCacheDownload(download_path, entry); + } } - connect(jarlibDownloadJob.data(), SIGNAL(succeeded()), SLOT(jarlibFinished())); - connect(jarlibDownloadJob.data(), SIGNAL(failed()), SLOT(jarlibFailed())); - connect(jarlibDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(jarlibFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); jarlibDownloadJob->start(); } @@ -159,6 +191,8 @@ void OneSixUpdate::jarlibFinished() void OneSixUpdate::jarlibFailed() { - emitFailed("Failed to download the binary garbage. Try again. Maybe. IF YOU DARE"); + QStringList failed = jarlibDownloadJob->getFailedFiles(); + QString failed_all = failed.join("\n"); + emitFailed("Failed to download the following files:\n" + failed_all + + "\n\nPlease try again."); } - diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h index 7314a6d1..ed8dcbcd 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -47,7 +47,7 @@ private: DownloadJobPtr jarlibDownloadJob; // target version, determined during this task - QSharedPointer<MinecraftVersion> targetVersion; + std::shared_ptr<MinecraftVersion> targetVersion; }; diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index 56e272e2..51958389 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -1,132 +1,311 @@ #include "OneSixVersion.h" +#include "OneSixLibrary.h" +#include "OneSixRule.h" -RuleAction RuleAction_fromString(QString name) +std::shared_ptr<OneSixVersion> fromJsonV4(QJsonObject root, + std::shared_ptr<OneSixVersion> fullVersion) { - if(name == "allow") - return Allow; - if(name == "disallow") - return Disallow; - return Defer; -} - -OpSys OpSys_fromString(QString name) -{ - if(name == "linux") - return Os_Linux; - if(name == "windows") - return Os_Windows; - if(name == "osx") - return Os_OSX; - return Os_Other; -} + fullVersion->id = root.value("id").toString(); -void Library::finalize() -{ - QStringList parts = m_name.split ( ':' ); - QString relative = parts[0]; - relative.replace ( '.','/' ); - relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; - if ( !m_is_native ) - relative += ".jar"; - else + fullVersion->mainClass = root.value("mainClass").toString(); + auto procArgsValue = root.value("processArguments"); + if (procArgsValue.isString()) { - if ( m_native_suffixes.contains ( currentSystem ) ) + fullVersion->processArguments = procArgsValue.toString(); + QString toCompare = fullVersion->processArguments.toLower(); + if (toCompare == "legacy") { - relative += "-" + m_native_suffixes[currentSystem] + ".jar"; + fullVersion->minecraftArguments = " ${auth_player_name} ${auth_session}"; } - else + else if (toCompare == "username_session") { - // really, bad. - relative += ".jar"; + fullVersion->minecraftArguments = + "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + fullVersion->minecraftArguments = "--username ${auth_player_name} " + "--session ${auth_session} " + "--version ${profile_name}"; } } - m_storage_path = relative; - m_download_path = m_base_url + relative; - if ( m_rules.empty() ) + auto minecraftArgsValue = root.value("minecraftArguments"); + if (minecraftArgsValue.isString()) { - m_is_active = true; + fullVersion->minecraftArguments = minecraftArgsValue.toString(); } - else + + auto minecraftTypeValue = root.value("type"); + if (minecraftTypeValue.isString()) { - RuleAction result = Disallow; - for ( auto rule: m_rules ) - { - RuleAction temp = rule->apply ( this ); - if ( temp != Defer ) - result = temp; - } - m_is_active = ( result == Allow ); + fullVersion->type = minecraftTypeValue.toString(); } - if ( m_is_native ) + + fullVersion->releaseTime = root.value("releaseTime").toString(); + fullVersion->time = root.value("time").toString(); + + // Iterate through the list, if it's a list. + auto librariesValue = root.value("libraries"); + if (!librariesValue.isArray()) + return fullVersion; + + QJsonArray libList = root.value("libraries").toArray(); + for (auto libVal : libList) { - m_is_active = m_is_active && m_native_suffixes.contains ( currentSystem ); + if (!libVal.isObject()) + { + continue; + } + + QJsonObject libObj = libVal.toObject(); + + // Library name + auto nameVal = libObj.value("name"); + if (!nameVal.isString()) + continue; + std::shared_ptr<OneSixLibrary> library(new OneSixLibrary(nameVal.toString())); + + auto urlVal = libObj.value("url"); + if (urlVal.isString()) + { + library->setBaseUrl(urlVal.toString()); + } + auto hintVal = libObj.value("MMC-hint"); + if (hintVal.isString()) + { + library->setHint(hintVal.toString()); + } + auto urlAbsVal = libObj.value("MMC-absoluteUrl"); + auto urlAbsuVal = libObj.value("MMC-absulute_url"); // compatibility + if (urlAbsVal.isString()) + { + library->setAbsoluteUrl(urlAbsVal.toString()); + } + else if(urlAbsuVal.isString()) + { + library->setAbsoluteUrl(urlAbsuVal.toString()); + } + // Extract excludes (if any) + auto extractVal = libObj.value("extract"); + if (extractVal.isObject()) + { + QStringList excludes; + auto extractObj = extractVal.toObject(); + auto excludesVal = extractObj.value("exclude"); + if (excludesVal.isArray()) + { + auto excludesList = excludesVal.toArray(); + for (auto excludeVal : excludesList) + { + if (excludeVal.isString()) + excludes.append(excludeVal.toString()); + } + library->extract_excludes = excludes; + } + } + + auto nativesVal = libObj.value("natives"); + if (nativesVal.isObject()) + { + library->setIsNative(); + auto nativesObj = nativesVal.toObject(); + auto iter = nativesObj.begin(); + while (iter != nativesObj.end()) + { + auto osType = OpSys_fromString(iter.key()); + if (osType == Os_Other) + continue; + if (!iter.value().isString()) + continue; + library->addNative(osType, iter.value().toString()); + iter++; + } + } + library->setRules(rulesFromJsonV4(libObj)); + library->finalize(); + fullVersion->libraries.append(library); } + return fullVersion; } -void Library::setName ( QString name ) +std::shared_ptr<OneSixVersion> OneSixVersion::fromJson(QJsonObject root) { - m_name = name; -} -void Library::setBaseUrl ( QString base_url ) -{ - m_base_url = base_url; + std::shared_ptr<OneSixVersion> readVersion(new OneSixVersion()); + int launcher_ver = readVersion->minimumLauncherVersion = + root.value("minimumLauncherVersion").toDouble(); + + // ADD MORE HERE :D + if (launcher_ver > 0 && launcher_ver <= 9) + return fromJsonV4(root, readVersion); + else + { + return std::shared_ptr<OneSixVersion>(); + } } -void Library::setIsNative() + +std::shared_ptr<OneSixVersion> OneSixVersion::fromFile(QString filepath) { - m_is_native = true; + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) + { + return std::shared_ptr<OneSixVersion>(); + } + + auto data = file.readAll(); + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) + { + return std::shared_ptr<OneSixVersion>(); + } + + if (!jsonDoc.isObject()) + { + return std::shared_ptr<OneSixVersion>(); + } + QJsonObject root = jsonDoc.object(); + auto version = fromJson(root); + if(version) + version->original_file = filepath; + return version; } -void Library::addNative ( OpSys os, QString suffix ) + +bool OneSixVersion::toOriginalFile() { - m_is_native = true; - m_native_suffixes[os] = suffix; + if (original_file.isEmpty()) + return false; + QSaveFile file(original_file); + if (!file.open(QIODevice::WriteOnly)) + { + return false; + } + // serialize base attributes (those we care about anyway) + QJsonObject root; + root.insert("minecraftArguments", minecraftArguments); + root.insert("mainClass", mainClass); + root.insert("minimumLauncherVersion", minimumLauncherVersion); + root.insert("time", time); + root.insert("id", id); + root.insert("type", type); + // screw processArguments + root.insert("releaseTime", releaseTime); + QJsonArray libarray; + for(const auto & lib: libraries) + { + libarray.append(lib->toJson()); + } + if(libarray.count()) + root.insert("libraries", libarray); + QJsonDocument doc(root); + file.write(doc.toJson()); + return file.commit(); } -void Library::setRules ( QList< QSharedPointer< Rule > > rules ) + +QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNormalLibs() { - m_rules = rules; + QList<std::shared_ptr<OneSixLibrary>> output; + for (auto lib : libraries) + { + if (lib->isActive() && !lib->isNative()) + { + output.append(lib); + } + } + return output; } -bool Library::isActive() + +QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNativeLibs() { - return m_is_active; + QList<std::shared_ptr<OneSixLibrary>> output; + for (auto lib : libraries) + { + if (lib->isActive() && lib->isNative()) + { + output.append(lib); + } + } + return output; } -bool Library::isNative() + +void OneSixVersion::externalUpdateStart() { - return m_is_native; + beginResetModel(); } -QString Library::downloadPath() + +void OneSixVersion::externalUpdateFinish() { - return m_download_path; + endResetModel(); } -QString Library::storagePath() + +QVariant OneSixVersion::data(const QModelIndex &index, int role) const { - return m_storage_path; -} + if (!index.isValid()) + return QVariant(); + int row = index.row(); + int column = index.column(); -QList<QSharedPointer<Library> > OneSixVersion::getActiveNormalLibs() -{ - QList<QSharedPointer<Library> > output; - for ( auto lib: libraries ) + if (row < 0 || row >= libraries.size()) + return QVariant(); + + if (role == Qt::DisplayRole) { - if (lib->isActive() && !lib->isNative()) + switch (column) { - output.append(lib); + case 0: + return libraries[row]->name(); + case 1: + return libraries[row]->type(); + case 2: + return libraries[row]->version(); + default: + return QVariant(); } } - return output; + return QVariant(); } -QList<QSharedPointer<Library> > OneSixVersion::getActiveNativeLibs() +Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const { - QList<QSharedPointer<Library> > output; - for ( auto lib: libraries ) + if (!index.isValid()) + return Qt::NoItemFlags; + int row = index.row(); + if (libraries[row]->isActive()) { - if (lib->isActive() && lib->isNative()) - { - output.append(lib); - } + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } + else + { + return Qt::ItemNeverHasChildren; + } + // return QAbstractListModel::flags(index); +} + +QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return QVariant(); + switch (section) + { + case 0: + return QString("Name"); + case 1: + return QString("Type"); + case 2: + return QString("Version"); + default: + return QString(); } - return output; } +int OneSixVersion::rowCount(const QModelIndex &parent) const +{ + return libraries.size(); +} +int OneSixVersion::columnCount(const QModelIndex &parent) const +{ + return 3; +} diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index 89b7c911..3529138c 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -1,155 +1,38 @@ #pragma once #include <QtCore> +#include <memory> -class Library; +class OneSixLibrary; -enum OpSys +class OneSixVersion : public QAbstractListModel { - Os_Windows, - Os_Linux, - Os_OSX, - Os_Other -}; - -OpSys OpSys_fromString(QString); - -#ifdef Q_OS_WIN32 - #define currentSystem Os_Windows -#else - #ifdef Q_OS_MAC - #define currentSystem Os_OSX - #else - #define currentSystem Os_Linux - #endif -#endif -enum RuleAction -{ - Allow, - Disallow, - Defer -}; - -RuleAction RuleAction_fromString(QString); - -class Rule -{ -protected: - RuleAction m_result; - virtual bool applies(Library * parent) = 0; + // Things required to implement the Qt list model public: - Rule(RuleAction result) - :m_result(result) {} - virtual ~Rule(){}; - RuleAction apply(Library * parent) - { - if(applies(parent)) - return m_result; - else - return Defer; - }; -}; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; -class OsRule : public Rule -{ -private: - // the OS - OpSys m_system; - // the OS version regexp - QString m_version_regexp; -protected: - virtual bool applies ( Library* ) - { - return (m_system == currentSystem); - } - OsRule(RuleAction result, OpSys system, QString version_regexp) - : Rule(result), m_system(system), m_version_regexp(version_regexp) {} + // serialization/deserialization public: - static QSharedPointer<OsRule> create(RuleAction result, OpSys system, QString version_regexp) - { - return QSharedPointer<OsRule> (new OsRule(result, system, version_regexp)); - } -}; + bool toOriginalFile(); + static std::shared_ptr<OneSixVersion> fromJson(QJsonObject root); + static std::shared_ptr<OneSixVersion> fromFile(QString filepath); -class ImplicitRule : public Rule -{ -protected: - virtual bool applies ( Library* ) - { - return true; - } - ImplicitRule(RuleAction result) - : Rule(result) {} public: - static QSharedPointer<ImplicitRule> create(RuleAction result) - { - return QSharedPointer<ImplicitRule> (new ImplicitRule(result)); - } -}; + QList<std::shared_ptr<OneSixLibrary>> getActiveNormalLibs(); + QList<std::shared_ptr<OneSixLibrary>> getActiveNativeLibs(); + // called when something starts/stops messing with the object + // FIXME: these are ugly in every possible way. + void externalUpdateStart(); + void externalUpdateFinish(); -class Library -{ -private: - // basic values used internally (so far) - QString m_name; - QString m_base_url; - QList<QSharedPointer<Rule> > m_rules; - - // derived values used for real things - /// where to store the lib locally - QString m_storage_path; - /// where to download the lib from - QString m_download_path; - /// is this lib actually active on the current OS? - bool m_is_active; - /// is the library a native? - bool m_is_native; - /// native suffixes per OS - QMap<OpSys, QString> m_native_suffixes; -public: - QStringList extract_excludes; - -public: - /// Constructor - Library(QString name) - { - m_is_native = false; - m_is_native = false; - m_name = name; - m_base_url = "https://s3.amazonaws.com/Minecraft.Download/libraries/"; - } - - /** - * finalize the library, processing the input values into derived values and state - * - * This SHALL be called after all the values are parsed or after any further change. - */ - void finalize(); - - /// Set the library composite name - void setName(QString name); - /// Set the url base for downloads - void setBaseUrl(QString base_url); - /// Call this to mark the library as 'native' (it's a zip archive with DLLs) - void setIsNative(); - /// Attach a name suffix to the specified OS native - void addNative(OpSys os, QString suffix); - /// Set the load rules - void setRules(QList<QSharedPointer<Rule> > rules); - - /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive(); - /// Returns true if the library is native - bool isNative(); - /// Get the URL to download the library from - QString downloadPath(); - /// Get the relative path where the library should be saved - QString storagePath(); -}; - - -class OneSixVersion -{ + // data members public: + /// file this was read from. blank, if none + QString original_file; /// the ID - determines which jar to use! ACTUALLY IMPORTANT! QString id; /// Last updated time - as a string @@ -162,26 +45,27 @@ public: * DEPRECATED: Old versions of the new vanilla launcher used this * ex: "username_session_version" */ - QString processArguments; + QString processArguments; /** * arguments that should be used for launching minecraft - * + * * ex: "--username ${auth_player_name} --session ${auth_session} * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" */ QString minecraftArguments; /** - * the minimum launcher version required by this version ... current is 4 (at point of writing) + * the minimum launcher version required by this version ... current is 4 (at point of + * writing) */ - int minimumLauncherVersion; + int minimumLauncherVersion = 0xDEADBEEF; /** * The main class to load first */ QString mainClass; - + /// the list of libs - both active and inactive, native and java - QList<QSharedPointer<Library> > libraries; - + QList<std::shared_ptr<OneSixLibrary>> libraries; + /* FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. @@ -197,17 +81,11 @@ public: } } ], - "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" + "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX + 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" } */ // QList<Rule> rules; - -public: - OneSixVersion() - { - minimumLauncherVersion = 0xDEADBEEF; - } - - QList<QSharedPointer<Library> > getActiveNormalLibs(); - QList<QSharedPointer<Library> > getActiveNativeLibs(); -};
\ No newline at end of file + + +}; diff --git a/logic/OpSys.cpp b/logic/OpSys.cpp new file mode 100644 index 00000000..f101fd08 --- /dev/null +++ b/logic/OpSys.cpp @@ -0,0 +1,23 @@ +#include "OpSys.h" + +OpSys OpSys_fromString(QString name) +{ + if(name == "linux") + return Os_Linux; + if(name == "windows") + return Os_Windows; + if(name == "osx") + return Os_OSX; + return Os_Other; +} + +QString OpSys_toString(OpSys name) +{ + switch(name) + { + case Os_Linux: return "linux"; + case Os_OSX: return "osx"; + case Os_Windows: return "windows"; + default: return "other"; + } +}
\ No newline at end of file diff --git a/logic/OpSys.h b/logic/OpSys.h new file mode 100644 index 00000000..aaa2eb65 --- /dev/null +++ b/logic/OpSys.h @@ -0,0 +1,22 @@ +#pragma once +#include <QString> +enum OpSys +{ + 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
\ No newline at end of file diff --git a/logic/VersionFactory.cpp b/logic/VersionFactory.cpp deleted file mode 100644 index 71c4d747..00000000 --- a/logic/VersionFactory.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "VersionFactory.h" -#include "OneSixVersion.h" - -// Library rules (if any) -QList<QSharedPointer<Rule> > FullVersionFactory::parse4rules(QJsonObject & baseObj) -{ - QList<QSharedPointer<Rule> > rules; - auto rulesVal = baseObj.value("rules"); - if(rulesVal.isArray()) - { - QJsonArray ruleList = rulesVal.toArray(); - for(auto ruleVal : ruleList) - { - QSharedPointer<Rule> rule; - if(!ruleVal.isObject()) - continue; - auto ruleObj = ruleVal.toObject(); - auto actionVal = ruleObj.value("action"); - if(!actionVal.isString()) - continue; - auto action = RuleAction_fromString(actionVal.toString()); - if(action == Defer) - continue; - - auto osVal = ruleObj.value("os"); - if(!osVal.isObject()) - { - // add a new implicit action rule - rules.append(ImplicitRule::create(action)); - } - else - { - auto osObj = osVal.toObject(); - auto osNameVal = osObj.value("name"); - if(!osNameVal.isString()) - continue; - OpSys requiredOs = OpSys_fromString(osNameVal.toString()); - QString versionRegex = osObj.value("version").toString(); - // add a new OS rule - rules.append(OsRule::create(action, requiredOs, versionRegex)); - } - } - } - return rules; -} - - -QSharedPointer<OneSixVersion> FullVersionFactory::parse4(QJsonObject root, QSharedPointer<OneSixVersion> fullVersion) -{ - fullVersion->id = root.value("id").toString(); - - fullVersion->mainClass = root.value("mainClass").toString(); - auto procArgsValue = root.value("processArguments"); - if(procArgsValue.isString()) - { - fullVersion->processArguments = procArgsValue.toString(); - QString toCompare = fullVersion->processArguments.toLower(); - if(toCompare == "legacy") - { - fullVersion->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if(toCompare == "username_session") - { - fullVersion->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if(toCompare == "username_session_version") - { - fullVersion->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; - } - } - - auto minecraftArgsValue = root.value("minecraftArguments"); - if(minecraftArgsValue.isString()) - { - fullVersion->minecraftArguments = minecraftArgsValue.toString(); - } - - auto minecraftTypeValue = root.value("type"); - if(minecraftTypeValue.isString()) - { - fullVersion->type = minecraftTypeValue.toString(); - } - - fullVersion->releaseTime = root.value("releaseTime").toString(); - fullVersion->time = root.value("time").toString(); - - // Iterate through the list, if it's a list. - auto librariesValue = root.value("libraries"); - if(!librariesValue.isArray()) - return fullVersion; - - QJsonArray libList = root.value("libraries").toArray(); - for (auto libVal : libList) - { - if (!libVal.isObject()) - { - continue; - } - - QJsonObject libObj = libVal.toObject(); - - // Library name - auto nameVal = libObj.value("name"); - if(!nameVal.isString()) - continue; - QSharedPointer<Library> library(new Library(nameVal.toString())); - - auto urlVal = libObj.value("url"); - if(urlVal.isString()) - { - library->setBaseUrl(urlVal.toString()); - } - - // Extract excludes (if any) - auto extractVal = libObj.value("extract"); - if(extractVal.isObject()) - { - QStringList excludes; - auto extractObj = extractVal.toObject(); - auto excludesVal = extractObj.value("exclude"); - if(!excludesVal.isArray()) - goto SKIP_EXTRACTS; - auto excludesList = excludesVal.toArray(); - for(auto excludeVal : excludesList) - { - if(excludeVal.isString()) - excludes.append(excludeVal.toString()); - } - library->extract_excludes = excludes; - } - SKIP_EXTRACTS: - - auto nativesVal = libObj.value("natives"); - if(nativesVal.isObject()) - { - library->setIsNative(); - auto nativesObj = nativesVal.toObject(); - auto iter = nativesObj.begin(); - while(iter != nativesObj.end()) - { - auto osType = OpSys_fromString(iter.key()); - if(osType == Os_Other) - continue; - if(!iter.value().isString()) - continue; - library->addNative(osType, iter.value().toString()); - iter++; - } - } - library->setRules(parse4rules(libObj)); - library->finalize(); - fullVersion->libraries.append(library); - } - return fullVersion; -} - -QSharedPointer<OneSixVersion> FullVersionFactory::parse(QByteArray data) -{ - QSharedPointer<OneSixVersion> readVersion(new OneSixVersion()); - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); - - if (jsonError.error != QJsonParseError::NoError) - { - error_string = QString( "Error reading version file :") + " " + jsonError.errorString(); - m_error = FullVersionFactory::ParseError; - return QSharedPointer<OneSixVersion>(); - } - - if(!jsonDoc.isObject()) - { - error_string = "Error reading version file."; - m_error = FullVersionFactory::ParseError; - return QSharedPointer<OneSixVersion>(); - } - QJsonObject root = jsonDoc.object(); - - int launcher_ver = readVersion->minimumLauncherVersion = root.value("minimumLauncherVersion").toDouble(); - // ADD MORE HERE :D - if(launcher_ver > 0 && launcher_ver <= 7) - return parse4(root, readVersion); - else - { - error_string = "Version file was for an unrecognized launcher version. RIP"; - m_error = FullVersionFactory::UnsupportedVersion; - return QSharedPointer<OneSixVersion>(); - } -} - - -FullVersionFactory::FullVersionFactory() -{ - m_error = FullVersionFactory::AllOK; -} diff --git a/logic/VersionFactory.h b/logic/VersionFactory.h deleted file mode 100644 index 0c0ee2d4..00000000 --- a/logic/VersionFactory.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include <QtCore> - -struct OneSixVersion; -class Rule; - -class FullVersionFactory -{ -public: - enum Error - { - AllOK, // all parsed OK - ParseError, // the file was corrupted somehow - UnsupportedVersion // the file was meant for a launcher version we don't support (yet) - } m_error; - QString error_string; - -public: - FullVersionFactory(); - QSharedPointer<OneSixVersion> parse(QByteArray data); -private: - QSharedPointer<OneSixVersion> parse4(QJsonObject root, QSharedPointer<OneSixVersion> product); - QList<QSharedPointer<Rule> > parse4rules(QJsonObject & baseObj); -};
\ No newline at end of file diff --git a/logic/lists/InstVersionList.cpp b/logic/lists/BaseVersionList.cpp index 7dc67155..61da5eeb 100644 --- a/logic/lists/InstVersionList.cpp +++ b/logic/lists/BaseVersionList.cpp @@ -13,33 +13,33 @@ * limitations under the License. */ -#include "logic/lists/InstVersionList.h" -#include "logic/InstanceVersion.h" +#include "logic/lists/BaseVersionList.h" +#include "logic/BaseVersion.h" -InstVersionList::InstVersionList(QObject *parent) : +BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent) { } -InstVersionPtr InstVersionList::findVersion( const QString& descriptor ) +BaseVersionPtr BaseVersionList::findVersion( const QString& descriptor ) { for (int i = 0; i < count(); i++) { - if (at(i)->descriptor == descriptor) + if (at(i)->descriptor() == descriptor) return at(i); } - return InstVersionPtr(); + return BaseVersionPtr(); } -InstVersionPtr InstVersionList::getLatestStable() const +BaseVersionPtr BaseVersionList::getLatestStable() const { if (count() <= 0) - return InstVersionPtr(); + return BaseVersionPtr(); else return at(0); } -QVariant InstVersionList::data(const QModelIndex &index, int role) const +QVariant BaseVersionList::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); @@ -48,7 +48,7 @@ QVariant InstVersionList::data(const QModelIndex &index, int role) const return QVariant(); - InstVersionPtr version = at(index.row()); + BaseVersionPtr version = at(index.row()); switch (role) { @@ -56,20 +56,17 @@ QVariant InstVersionList::data(const QModelIndex &index, int role) const switch (index.column()) { case NameColumn: - return version->name; + return version->name(); case TypeColumn: return version->typeString(); - case TimeColumn: - return version->timestamp; - default: return QVariant(); } case Qt::ToolTipRole: - return version->descriptor; + return version->descriptor(); case VersionPointerRole: return qVariantFromValue(version); @@ -79,7 +76,7 @@ QVariant InstVersionList::data(const QModelIndex &index, int role) const } } -QVariant InstVersionList::headerData(int section, Qt::Orientation orientation, int role) const +QVariant BaseVersionList::headerData(int section, Qt::Orientation orientation, int role) const { switch (role) { @@ -91,9 +88,6 @@ QVariant InstVersionList::headerData(int section, Qt::Orientation orientation, i case TypeColumn: return "Type"; - - case TimeColumn: - return "Time"; default: return QVariant(); @@ -117,13 +111,13 @@ QVariant InstVersionList::headerData(int section, Qt::Orientation orientation, i } } -int InstVersionList::rowCount(const QModelIndex &parent) const +int BaseVersionList::rowCount(const QModelIndex &parent) const { // Return count return count(); } -int InstVersionList::columnCount(const QModelIndex &parent) const +int BaseVersionList::columnCount(const QModelIndex &parent) const { return 2; } diff --git a/logic/lists/InstVersionList.h b/logic/lists/BaseVersionList.h index bc6aa5d4..d37431ed 100644 --- a/logic/lists/InstVersionList.h +++ b/logic/lists/BaseVersionList.h @@ -20,7 +20,7 @@ #include <QAbstractListModel> #include <QSharedPointer> -#include "logic/InstanceVersion.h" +#include "logic/BaseVersion.h" class Task; @@ -36,7 +36,7 @@ class Task; * all have a default implementation, but they can be overridden by plugins to * change the behavior of the list. */ -class InstVersionList : public QAbstractListModel +class BaseVersionList : public QAbstractListModel { Q_OBJECT public: @@ -57,7 +57,7 @@ public: TimeColumn }; - explicit InstVersionList(QObject *parent = 0); + explicit BaseVersionList(QObject *parent = 0); /*! * \brief Gets a task that will reload the version list. @@ -71,7 +71,7 @@ public: virtual bool isLoaded() = 0; //! Gets the version at the given index. - virtual const InstVersionPtr at(int i) const = 0; + virtual const BaseVersionPtr at(int i) const = 0; //! Returns the number of versions in the list. virtual int count() const = 0; @@ -90,14 +90,14 @@ public: * \return A const pointer to the version with the given descriptor. NULL if * one doesn't exist. */ - virtual InstVersionPtr findVersion(const QString &descriptor); + virtual BaseVersionPtr findVersion(const QString &descriptor); /*! * \brief Gets the latest stable version of this instance type. * This is the version that will be selected by default. * By default, this is simply the first version in the list. */ - virtual InstVersionPtr getLatestStable() const; + virtual BaseVersionPtr getLatestStable() const; /*! * Sorts the version list. @@ -117,5 +117,5 @@ protected slots: * then copies the versions and sets their parents correctly. * \param versions List of versions whose parents should be set. */ - virtual void updateListData(QList<InstVersionPtr > versions) = 0; + virtual void updateListData(QList<BaseVersionPtr > versions) = 0; }; diff --git a/logic/lists/ForgeVersionList.cpp b/logic/lists/ForgeVersionList.cpp new file mode 100644 index 00000000..491a43d7 --- /dev/null +++ b/logic/lists/ForgeVersionList.cpp @@ -0,0 +1,297 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ForgeVersionList.h" +#include <logic/net/DownloadJob.h> +#include "MultiMC.h" + +#include <QtNetwork> +#include <QtXml> +#include <QRegExp> + +#include <logger/QsLog.h> + +#define JSON_URL "http://files.minecraftforge.net/minecraftforge/json" + +ForgeVersionList::ForgeVersionList(QObject *parent) : BaseVersionList(parent) +{ +} + +Task *ForgeVersionList::getLoadTask() +{ + return new ForgeListLoadTask(this); +} + +bool ForgeVersionList::isLoaded() +{ + return m_loaded; +} + +const BaseVersionPtr ForgeVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +int ForgeVersionList::count() const +{ + return m_vlist.count(); +} + +int ForgeVersionList::columnCount(const QModelIndex &parent) const +{ + return 3; +} + +QVariant ForgeVersionList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + auto version = std::dynamic_pointer_cast<ForgeVersion>(m_vlist[index.row()]); + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case 0: + return version->name(); + + case 1: + return version->mcver; + + case 2: + return version->typeString(); + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return version->descriptor(); + + case VersionPointerRole: + return qVariantFromValue(m_vlist[index.row()]); + + default: + return QVariant(); + } +} + +QVariant ForgeVersionList::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case 0: + return "Version"; + + case 1: + return "Minecraft"; + + case 2: + return "Type"; + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case 0: + return "The name of the version."; + + case 1: + return "Minecraft version"; + + case 2: + return "The version's type."; + + default: + return QVariant(); + } + + default: + return QVariant(); + } +} + +BaseVersionPtr ForgeVersionList::getLatestStable() const +{ + return BaseVersionPtr(); +} + +void ForgeVersionList::updateListData(QList<BaseVersionPtr> versions) +{ + beginResetModel(); + m_vlist = versions; + m_loaded = true; + endResetModel(); + // NOW SORT!! + // sort(); +} + +void ForgeVersionList::sort() +{ + // NO-OP for now +} + +ForgeListLoadTask::ForgeListLoadTask(ForgeVersionList *vlist) : Task() +{ + m_list = vlist; +} + +void ForgeListLoadTask::executeTask() +{ + auto job = new DownloadJob("Version index"); + // we do not care if the version is stale or not. + auto forgeListEntry = MMC->metacache()->resolveEntry("minecraftforge", "list.json"); + + // verify by poking the server. + forgeListEntry->stale = true; + + job->addCacheDownload(QUrl(JSON_URL), forgeListEntry); + listJob.reset(job); + connect(listJob.get(), SIGNAL(succeeded()), SLOT(list_downloaded())); + connect(listJob.get(), SIGNAL(failed()), SLOT(list_failed())); + connect(listJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); + listJob->start(); +} + +void ForgeListLoadTask::list_failed() +{ + auto DlJob = listJob->first(); + auto reply = DlJob->m_reply; + if(reply) + { + QLOG_ERROR() << "Getting forge version list failed: " << reply->errorString(); + } + else + QLOG_ERROR() << "Getting forge version list failed for reasons unknown."; +} + +void ForgeListLoadTask::list_downloaded() +{ + QByteArray data; + { + auto DlJob = listJob->first(); + auto filename = std::dynamic_pointer_cast<CacheDownload>(DlJob)->m_target_path; + QFile listFile(filename); + if(!listFile.open(QIODevice::ReadOnly)) + return; + data = listFile.readAll(); + DlJob.reset(); + } + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed("Error parsing version list JSON:" + jsonError.errorString()); + return; + } + + if (!jsonDoc.isObject()) + { + emitFailed("Error parsing version list JSON: jsonDoc is not an object"); + return; + } + + QJsonObject root = jsonDoc.object(); + + // Now, get the array of versions. + if (!root.value("builds").isArray()) + { + emitFailed( + "Error parsing version list JSON: version list object is missing 'builds' array"); + return; + } + QJsonArray builds = root.value("builds").toArray(); + + QList<BaseVersionPtr> tempList; + for (int i = 0; i < builds.count(); i++) + { + // Load the version info. + if (!builds[i].isObject()) + { + // FIXME: log this somewhere + continue; + } + QJsonObject obj = builds[i].toObject(); + int build_nr = obj.value("build").toDouble(0); + if (!build_nr) + continue; + QJsonArray files = obj.value("files").toArray(); + QString url, jobbuildver, mcver, buildtype, filename; + QString changelog_url, installer_url; + QString installer_filename; + bool valid = false; + for (int j = 0; j < files.count(); j++) + { + if (!files[j].isObject()) + continue; + QJsonObject file = files[j].toObject(); + buildtype = file.value("buildtype").toString(); + if ((buildtype == "client" || buildtype == "universal") && !valid) + { + mcver = file.value("mcver").toString(); + url = file.value("url").toString(); + jobbuildver = file.value("jobbuildver").toString(); + int lastSlash = url.lastIndexOf('/'); + filename = url.mid(lastSlash + 1); + valid = true; + } + else if (buildtype == "changelog") + { + QString ext = file.value("ext").toString(); + if (ext.isEmpty()) + continue; + changelog_url = file.value("url").toString(); + } + else if (buildtype == "installer") + { + installer_url = file.value("url").toString(); + int lastSlash = installer_url.lastIndexOf('/'); + installer_filename = installer_url.mid(lastSlash + 1); + } + } + if (valid) + { + // Now, we construct the version object and add it to the list. + std::shared_ptr<ForgeVersion> fVersion(new ForgeVersion()); + fVersion->universal_url = url; + fVersion->changelog_url = changelog_url; + fVersion->installer_url = installer_url; + fVersion->jobbuildver = jobbuildver; + fVersion->mcver = mcver; + if (installer_filename.isEmpty()) + fVersion->filename = filename; + else + fVersion->filename = installer_filename; + fVersion->m_buildnr = build_nr; + tempList.append(fVersion); + } + } + m_list->updateListData(tempList); + + emitSucceeded(); + return; +} diff --git a/logic/lists/ForgeVersionList.h b/logic/lists/ForgeVersionList.h new file mode 100644 index 00000000..56a207c8 --- /dev/null +++ b/logic/lists/ForgeVersionList.h @@ -0,0 +1,109 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QObject> +#include <QAbstractListModel> +#include <QSharedPointer> +#include <QUrl> + +#include <QNetworkReply> +#include "BaseVersionList.h" +#include "logic/tasks/Task.h" +#include "logic/net/DownloadJob.h" + +class ForgeVersion; +typedef std::shared_ptr<ForgeVersion> ForgeVersionPtr; + +struct ForgeVersion : public BaseVersion +{ + virtual QString descriptor() + { + return filename; + } + ; + virtual QString name() + { + return "Forge " + jobbuildver; + } + ; + virtual QString typeString() const + { + if (installer_url.isEmpty()) + return "Universal"; + else + return "Installer"; + } + ; + + int m_buildnr = 0; + QString universal_url; + QString changelog_url; + QString installer_url; + QString jobbuildver; + QString mcver; + QString filename; +}; + +class ForgeVersionList : public BaseVersionList +{ + Q_OBJECT +public: + friend class ForgeListLoadTask; + + explicit ForgeVersionList(QObject *parent = 0); + + virtual Task *getLoadTask(); + virtual bool isLoaded(); + virtual const BaseVersionPtr at(int i) const; + virtual int count() const; + virtual void sort(); + + virtual BaseVersionPtr getLatestStable() const; + + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role) const; + virtual int columnCount(const QModelIndex &parent) const; + +protected: + QList<BaseVersionPtr> m_vlist; + + bool m_loaded; + +protected +slots: + virtual void updateListData(QList<BaseVersionPtr> versions); +}; + +class ForgeListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit ForgeListLoadTask(ForgeVersionList *vlist); + + virtual void executeTask(); + +protected +slots: + void list_downloaded(); + void list_failed(); + +protected: + DownloadJobPtr listJob; + ForgeVersionList *m_list; +}; diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index b930f781..9740d5a5 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -3,7 +3,7 @@ * 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 @@ -29,13 +29,13 @@ #include "logic/lists/IconList.h" #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" +#include <logger/QsLog.h> const static int GROUP_FILE_FORMAT_VERSION = 1; -InstanceList::InstanceList(const QString &instDir, QObject *parent) : - QAbstractListModel ( parent ), m_instDir("instances") +InstanceList::InstanceList(const QString &instDir, QObject *parent) + : QAbstractListModel(parent), m_instDir("instances") { - } InstanceList::~InstanceList() @@ -43,32 +43,32 @@ InstanceList::~InstanceList() saveGroupList(); } -int InstanceList::rowCount ( const QModelIndex& parent ) const +int InstanceList::rowCount(const QModelIndex &parent) const { - Q_UNUSED ( parent ); + Q_UNUSED(parent); return m_instances.count(); } -QModelIndex InstanceList::index ( int row, int column, const QModelIndex& parent ) const +QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const { - Q_UNUSED ( parent ); - if ( row < 0 || row >= m_instances.size() ) + Q_UNUSED(parent); + if (row < 0 || row >= m_instances.size()) return QModelIndex(); - return createIndex ( row, column, ( void* ) m_instances.at ( row ).data() ); + return createIndex(row, column, (void *)m_instances.at(row).get()); } -QVariant InstanceList::data ( const QModelIndex& index, int role ) const +QVariant InstanceList::data(const QModelIndex &index, int role) const { - if ( !index.isValid() ) + if (!index.isValid()) { return QVariant(); } - BaseInstance *pdata = static_cast<BaseInstance*> ( index.internalPointer() ); - switch ( role ) + BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer()); + switch (role) { case InstancePointerRole: { - QVariant v = qVariantFromValue((void *) pdata); + QVariant v = qVariantFromValue((void *)pdata); return v; } case Qt::DisplayRole: @@ -96,12 +96,12 @@ QVariant InstanceList::data ( const QModelIndex& index, int role ) const return QVariant(); } -Qt::ItemFlags InstanceList::flags ( const QModelIndex& index ) const +Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const { Qt::ItemFlags f; - if ( index.isValid() ) + if (index.isValid()) { - f |= ( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); + f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable); } return f; } @@ -116,23 +116,23 @@ void InstanceList::saveGroupList() { QString groupFileName = m_instDir + "/instgroups.json"; QFile groupFile(groupFileName); - + // if you can't open the file, fail - if (!groupFile.open(QIODevice::WriteOnly| QIODevice::Truncate)) + if (!groupFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { // An error occurred. Ignore it. - qDebug("Failed to read instance group file."); + QLOG_ERROR() << "Failed to read instance group file."; return; } QTextStream out(&groupFile); - QMap<QString, QSet<QString> > groupMap; - for(auto instance: m_instances) + QMap<QString, QSet<QString>> groupMap; + for (auto instance : m_instances) { QString id = instance->id(); QString group = instance->group(); - if(group.isEmpty()) + if (group.isEmpty()) continue; - if(!groupMap.count(group)) + if (!groupMap.count(group)) { QSet<QString> set; set.insert(id); @@ -145,109 +145,114 @@ void InstanceList::saveGroupList() } } QJsonObject toplevel; - toplevel.insert("formatVersion",QJsonValue(QString("1"))); + toplevel.insert("formatVersion", QJsonValue(QString("1"))); QJsonObject groupsArr; - for(auto iter = groupMap.begin(); iter != groupMap.end(); iter++) + 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) + groupObj.insert("hidden", QJsonValue(QString("false"))); + for (auto item : list) { instanceArr.append(QJsonValue(item)); } - groupObj.insert("instances",instanceArr); - groupsArr.insert(name,groupObj); + groupObj.insert("instances", instanceArr); + groupsArr.insert(name, groupObj); } - toplevel.insert("groups",groupsArr); + toplevel.insert("groups", groupsArr); QJsonDocument doc(toplevel); groupFile.write(doc.toJson()); groupFile.close(); } -void InstanceList::loadGroupList(QMap<QString, QString> & groupMap) +void InstanceList::loadGroupList(QMap<QString, QString> &groupMap) { QString groupFileName = m_instDir + "/instgroups.json"; - + // if there's no group file, fail - if(!QFileInfo(groupFileName).exists()) + if (!QFileInfo(groupFileName).exists()) return; - + QFile groupFile(groupFileName); - + // if you can't open the file, fail if (!groupFile.open(QIODevice::ReadOnly)) { // An error occurred. Ignore it. - qDebug("Failed to read instance group file."); + QLOG_ERROR() << "Failed to read instance group file."; return; } - + QTextStream in(&groupFile); QString jsonStr = in.readAll(); groupFile.close(); - + QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &error); - + // if the json was bad, fail if (error.error != QJsonParseError::NoError) { - qWarning(QString("Failed to parse instance group file: %1 at offset %2"). - arg(error.errorString(), QString::number(error.offset)).toUtf8()); + QLOG_ERROR() << 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()); + "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()); + "It should contain an array " + "called 'instances'.") + .arg(groupName) + .toUtf8()); continue; } - + // 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++) + + for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); + iter2++) { groupMap[(*iter2).toString()] = groupName; } @@ -259,9 +264,9 @@ InstanceList::InstListError InstanceList::loadList() // load the instance groups QMap<QString, QString> groupMap; loadGroupList(groupMap); - + beginResetModel(); - + m_instances.clear(); QDir dir(m_instDir); QDirIterator iter(dir); @@ -270,53 +275,55 @@ InstanceList::InstListError InstanceList::loadList() QString subDir = iter.next(); if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists()) continue; - + BaseInstance *instPtr = NULL; auto &loader = InstanceFactory::get(); auto error = loader.loadInstance(instPtr, subDir); - - switch(error) + + switch (error) { - case InstanceFactory::NoLoadError: - break; - case InstanceFactory::NotAnInstance: - break; + case InstanceFactory::NoLoadError: + break; + case InstanceFactory::NotAnInstance: + break; } - - if (error != InstanceFactory::NoLoadError && - error != InstanceFactory::NotAnInstance) + + if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance) { - QString errorMsg = QString("Failed to load instance %1: "). - arg(QFileInfo(subDir).baseName()).toUtf8(); - + QString errorMsg = QString("Failed to load instance %1: ") + .arg(QFileInfo(subDir).baseName()) + .toUtf8(); + switch (error) { default: - errorMsg += QString("Unknown instance loader error %1"). - arg(error); + errorMsg += QString("Unknown instance loader error %1").arg(error); break; } - qDebug(errorMsg.toUtf8()); + QLOG_ERROR() << errorMsg.toUtf8(); } else if (!instPtr) { - qDebug(QString("Error loading instance %1. Instance loader returned null."). - arg(QFileInfo(subDir).baseName()).toUtf8()); + QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.") + .arg(QFileInfo(subDir).baseName()) + .toUtf8(); } else { - QSharedPointer<BaseInstance> inst(instPtr); + std::shared_ptr<BaseInstance> inst(instPtr); auto iter = groupMap.find(inst->id()); - if(iter != groupMap.end()) + if (iter != groupMap.end()) { inst->setGroupInitial((*iter)); } - qDebug(QString("Loaded instance %1").arg(inst->name()).toUtf8()); + QLOG_INFO() << "Loaded instance " << inst->name(); inst->setParent(this); m_instances.append(inst); - connect(instPtr, SIGNAL(propertiesChanged(BaseInstance*)),this, SLOT(propertiesChanged(BaseInstance*))); - connect(instPtr, SIGNAL(groupChanged()),this, SLOT(groupChanged())); - connect(instPtr, SIGNAL(nuked(BaseInstance*)), this, SLOT(instanceNuked(BaseInstance*))); + connect(instPtr, SIGNAL(propertiesChanged(BaseInstance *)), this, + SLOT(propertiesChanged(BaseInstance *))); + connect(instPtr, SIGNAL(groupChanged()), this, SLOT(groupChanged())); + connect(instPtr, SIGNAL(nuked(BaseInstance *)), this, + SLOT(instanceNuked(BaseInstance *))); } } endResetModel(); @@ -332,7 +339,8 @@ void InstanceList::clear() m_instances.clear(); endResetModel(); emit dataIsInvalid(); -}; +} +; /// Add an instance. Triggers notifications, returns the new index int InstanceList::add(InstancePtr t) @@ -340,9 +348,10 @@ int InstanceList::add(InstancePtr t) beginInsertRows(QModelIndex(), m_instances.size(), m_instances.size()); m_instances.append(t); t->setParent(this); - connect(t.data(), SIGNAL(propertiesChanged(BaseInstance*)),this, SLOT(propertiesChanged(BaseInstance*))); - connect(t.data(), SIGNAL(groupChanged()),this, SLOT(groupChanged())); - connect(t.data(), SIGNAL(nuked(BaseInstance*)), this, SLOT(instanceNuked(BaseInstance*))); + 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; } @@ -351,7 +360,7 @@ InstancePtr InstanceList::getInstanceById(QString instId) { QListIterator<InstancePtr> iter(m_instances); InstancePtr inst; - while(iter.hasNext()) + while (iter.hasNext()) { inst = iter.next(); if (inst->id() == instId) @@ -363,11 +372,11 @@ InstancePtr InstanceList::getInstanceById(QString instId) return iter.peekPrevious(); } -int InstanceList::getInstIndex ( BaseInstance* inst ) +int InstanceList::getInstIndex(BaseInstance *inst) { - for(int i = 0; i < m_instances.count(); i++) + for (int i = 0; i < m_instances.count(); i++) { - if(inst == m_instances[i].data()) + if (inst == m_instances[i].get()) { return i; } @@ -375,40 +384,39 @@ int InstanceList::getInstIndex ( BaseInstance* inst ) return -1; } - -void InstanceList::instanceNuked ( BaseInstance* inst ) +void InstanceList::instanceNuked(BaseInstance *inst) { int i = getInstIndex(inst); - if(i != -1) + if (i != -1) { - beginRemoveRows(QModelIndex(),i,i); + beginRemoveRows(QModelIndex(), i, i); m_instances.removeAt(i); endRemoveRows(); } } - -void InstanceList::propertiesChanged(BaseInstance * inst) +void InstanceList::propertiesChanged(BaseInstance *inst) { int i = getInstIndex(inst); - if(i != -1) + if (i != -1) { emit dataChanged(index(i), index(i)); } } -InstanceProxyModel::InstanceProxyModel ( QObject *parent ) - : KCategorizedSortFilterProxyModel ( parent ) +InstanceProxyModel::InstanceProxyModel(QObject *parent) + : KCategorizedSortFilterProxyModel(parent) { // disable since by default we are globally sorting by date: setCategorizedModel(true); } -bool InstanceProxyModel::subSortLessThan (const QModelIndex& left, const QModelIndex& right ) const +bool InstanceProxyModel::subSortLessThan(const QModelIndex &left, + const QModelIndex &right) const { - BaseInstance *pdataLeft = static_cast<BaseInstance*> ( left.internalPointer() ); - BaseInstance *pdataRight = static_cast<BaseInstance*> ( right.internalPointer() ); - //kDebug() << *pdataLeft << *pdataRight; + BaseInstance *pdataLeft = static_cast<BaseInstance *>(left.internalPointer()); + BaseInstance *pdataRight = static_cast<BaseInstance *>(right.internalPointer()); + // kDebug() << *pdataLeft << *pdataRight; return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; - //return pdataLeft->name() < pdataRight->name(); + // return pdataLeft->name() < pdataRight->name(); } diff --git a/logic/lists/JavaVersionList.cpp b/logic/lists/JavaVersionList.cpp new file mode 100644 index 00000000..5389c4cc --- /dev/null +++ b/logic/lists/JavaVersionList.cpp @@ -0,0 +1,203 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "JavaVersionList.h" +#include "MultiMC.h" + +#include <QtNetwork> +#include <QtXml> +#include <QRegExp> + +#include <logger/QsLog.h> +#include <logic/JavaUtils.h> + +JavaVersionList::JavaVersionList(QObject *parent) : BaseVersionList(parent) +{ +} + +Task *JavaVersionList::getLoadTask() +{ + return new JavaListLoadTask(this); +} + + +const BaseVersionPtr JavaVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +bool JavaVersionList::isLoaded() +{ + return m_loaded; +} + +int JavaVersionList::count() const +{ + return m_vlist.count(); +} + +int JavaVersionList::columnCount(const QModelIndex &parent) const +{ + return 4; +} + +QVariant JavaVersionList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + auto version = std::dynamic_pointer_cast<JavaVersion>(m_vlist[index.row()]); + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case 0: + return version->id; + + case 1: + return version->arch; + + case 2: + return version->path; + + case 3: + return version->recommended ? tr("Yes") : tr("No"); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return version->descriptor(); + + case VersionPointerRole: + return qVariantFromValue(m_vlist[index.row()]); + + default: + return QVariant(); + } +} + +QVariant JavaVersionList::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case 0: + return "Version"; + + case 1: + return "Arch"; + + case 2: + return "Path"; + + case 3: + return "Recommended"; + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case 0: + return "The name of the version."; + + case 1: + return "The architecture this version is for."; + + case 2: + return "Path to this Java version."; + + case 3: + return "Whether the version is recommended or not."; + + default: + return QVariant(); + } + + default: + return QVariant(); + } +} + +BaseVersionPtr JavaVersionList::getTopRecommended() const +{ + for (int i = 0; i < m_vlist.length(); i++) + { + auto ver = std::dynamic_pointer_cast<JavaVersion>(m_vlist.at(i)); + if (ver->recommended) + { + return m_vlist.at(i); + } + } + return BaseVersionPtr(); +} + +void JavaVersionList::updateListData(QList<BaseVersionPtr> versions) +{ + beginResetModel(); + m_vlist = versions; + m_loaded = true; + endResetModel(); + // NOW SORT!! + // sort(); +} + +void JavaVersionList::sort() +{ + // NO-OP for now +} + +JavaListLoadTask::JavaListLoadTask(JavaVersionList *vlist) +{ + m_list = vlist; + m_currentRecommended = NULL; +} + +JavaListLoadTask::~JavaListLoadTask() +{ +} + +void JavaListLoadTask::executeTask() +{ + setStatus("Detecting Java installations..."); + + JavaUtils ju; + QList<JavaVersionPtr> javas = ju.FindJavaPaths(); + + QList<BaseVersionPtr> javas_bvp; + for(int i = 0; i < javas.length(); i++) + { + BaseVersionPtr java = std::dynamic_pointer_cast<BaseVersion>(javas.at(i)); + + if(java) + { + javas_bvp.append(java); + } + } + + m_list->updateListData(javas_bvp); + + emitSucceeded(); +} diff --git a/logic/lists/JavaVersionList.h b/logic/lists/JavaVersionList.h new file mode 100644 index 00000000..23bccfe4 --- /dev/null +++ b/logic/lists/JavaVersionList.h @@ -0,0 +1,93 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QObject> +#include <QAbstractListModel> +#include <QSharedPointer> + +#include "BaseVersionList.h" +#include "logic/tasks/Task.h" + +class JavaListLoadTask; + +struct JavaVersion : public BaseVersion +{ + virtual QString descriptor() + { + return id; + } + + virtual QString name() + { + return id; + } + + virtual QString typeString() const + { + return arch; + } + + QString id; + QString arch; + QString path; + bool recommended; +}; + +typedef std::shared_ptr<JavaVersion> JavaVersionPtr; + +class JavaVersionList : public BaseVersionList +{ + Q_OBJECT +public: + explicit JavaVersionList(QObject *parent = 0); + + virtual Task *getLoadTask(); + virtual bool isLoaded(); + virtual const BaseVersionPtr at(int i) const; + virtual int count() const; + virtual void sort(); + + virtual BaseVersionPtr getTopRecommended() const; + + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role) const; + virtual int columnCount(const QModelIndex &parent) const; + +public slots: + virtual void updateListData(QList<BaseVersionPtr> versions); + +protected: + QList<BaseVersionPtr> m_vlist; + + bool m_loaded = false; +}; + +class JavaListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit JavaListLoadTask(JavaVersionList *vlist); + ~JavaListLoadTask(); + + virtual void executeTask(); + +protected: + JavaVersionList *m_list; + JavaVersion *m_currentRecommended; +}; diff --git a/logic/lists/LwjglVersionList.cpp b/logic/lists/LwjglVersionList.cpp index d7826a82..6bf401ec 100644 --- a/logic/lists/LwjglVersionList.cpp +++ b/logic/lists/LwjglVersionList.cpp @@ -3,7 +3,7 @@ * 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 @@ -17,23 +17,14 @@ #include "MultiMC.h" #include <QtNetwork> - #include <QtXml> - #include <QRegExp> -#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss" - -LWJGLVersionList mainVersionList; - -LWJGLVersionList &LWJGLVersionList::get() -{ - return mainVersionList; -} +#include <logger/QsLog.h> +#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss" -LWJGLVersionList::LWJGLVersionList(QObject *parent) : - QAbstractListModel(parent) +LWJGLVersionList::LWJGLVersionList(QObject *parent) : QAbstractListModel(parent) { setLoading(false); } @@ -42,20 +33,20 @@ QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); - + if (index.row() > count()) return QVariant(); - + const PtrLWJGLVersion version = at(index.row()); - + switch (role) { case Qt::DisplayRole: return version->name(); - + case Qt::ToolTipRole: return version->url(); - + default: return QVariant(); } @@ -67,10 +58,10 @@ QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, { case Qt::DisplayRole: return "Version"; - + case Qt::ToolTipRole: return "LWJGL version name."; - + default: return QVariant(); } @@ -89,7 +80,7 @@ bool LWJGLVersionList::isLoading() const void LWJGLVersionList::loadList() { Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)"); - + setLoading(true); auto worker = MMC->qnam(); reply = worker->get(QNetworkRequest(QUrl(RSS_URL))); @@ -110,68 +101,68 @@ void LWJGLVersionList::netRequestComplete() if (reply->error() == QNetworkReply::NoError) { QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip"); - Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", - "LWJGL regex is invalid"); - + Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", "LWJGL regex is invalid"); + QDomDocument doc; - + QString xmlErrorMsg; int errorLine; if (!doc.setContent(reply->readAll(), false, &xmlErrorMsg, &errorLine)) { - failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + QString::number(errorLine)); + failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + + QString::number(errorLine)); setLoading(false); return; } - + QDomNodeList items = doc.elementsByTagName("item"); - + QList<PtrLWJGLVersion> tempList; - + for (int i = 0; i < items.length(); i++) { Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list", "XML element isn't an element... wat?"); - + QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link"); if (linkElement.isNull()) { qWarning() << "Link element" << i << "in RSS feed doesn't exist! Skipping."; continue; } - + QString link = linkElement.text(); - + // Make sure it's a download link. if (link.endsWith("/download") && link.contains(lwjglRegex)) { QString name = link.mid(lwjglRegex.indexIn(link) + 6); // Subtract 4 here to remove the .zip file extension. name = name.left(lwjglRegex.matchedLength() - 10); - + QUrl url(link); if (!url.isValid()) { qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping."; continue; } - + tempList.append(LWJGLVersion::Create(name, link)); } } - + beginResetModel(); m_vlist.swap(tempList); endResetModel(); - - qDebug("Loaded LWJGL list."); + + QLOG_INFO() << "Loaded LWJGL list."; finished(); } else { failed("Failed to load LWJGL list. Network error: " + reply->errorString()); } - + setLoading(false); reply->deleteLater(); } @@ -181,13 +172,12 @@ const PtrLWJGLVersion LWJGLVersionList::getVersion(const QString &versionName) for (int i = 0; i < count(); i++) { QString name = at(i)->name(); - if ( name == versionName) + if (name == versionName) return at(i); } return PtrLWJGLVersion(); } - void LWJGLVersionList::failed(QString msg) { qWarning() << msg; diff --git a/logic/lists/LwjglVersionList.h b/logic/lists/LwjglVersionList.h index 638a0b67..99712292 100644 --- a/logic/lists/LwjglVersionList.h +++ b/logic/lists/LwjglVersionList.h @@ -17,13 +17,13 @@ #include <QObject> #include <QAbstractListModel> -#include <QSharedPointer> #include <QUrl> - #include <QNetworkReply> +#include <memory> + class LWJGLVersion; -typedef QSharedPointer<LWJGLVersion> PtrLWJGLVersion; +typedef std::shared_ptr<LWJGLVersion> PtrLWJGLVersion; class LWJGLVersion : public QObject { @@ -53,8 +53,6 @@ class LWJGLVersionList : public QAbstractListModel public: explicit LWJGLVersionList(QObject *parent = 0); - static LWJGLVersionList &get(); - bool isLoaded() { return m_vlist.length() > 0; } const PtrLWJGLVersion getVersion(const QString &versionName); diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp index 42fb1b50..dd26714a 100644 --- a/logic/lists/MinecraftVersionList.cpp +++ b/logic/lists/MinecraftVersionList.cpp @@ -3,7 +3,7 @@ * 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 @@ -16,8 +16,6 @@ #include "MinecraftVersionList.h" #include <MultiMC.h> -#include <QDebug> - #include <QtXml> #include <QJsonDocument> @@ -34,12 +32,8 @@ #define ASSETS_URLBASE "http://assets.minecraft.net/" #define MCN_URLBASE "http://sonicrules.org/mcnweb.py" -MinecraftVersionList mcVList; - -MinecraftVersionList::MinecraftVersionList(QObject *parent) : - InstVersionList(parent) +MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) { - } Task *MinecraftVersionList::getLoadTask() @@ -52,7 +46,7 @@ bool MinecraftVersionList::isLoaded() return m_loaded; } -const InstVersionPtr MinecraftVersionList::at(int i) const +const BaseVersionPtr MinecraftVersionList::at(int i) const { return m_vlist.at(i); } @@ -62,11 +56,11 @@ int MinecraftVersionList::count() const return m_vlist.count(); } -bool cmpVersions(InstVersionPtr first, InstVersionPtr second) +bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) { - const InstVersion & left = *first; - const InstVersion & right = *second; - return left > right; + auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); + auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); + return left->timestamp > right->timestamp; } void MinecraftVersionList::sort() @@ -76,25 +70,20 @@ void MinecraftVersionList::sort() endResetModel(); } -InstVersionPtr MinecraftVersionList::getLatestStable() const +BaseVersionPtr MinecraftVersionList::getLatestStable() const { for (int i = 0; i < m_vlist.length(); i++) { - auto ver = m_vlist.at(i).dynamicCast<MinecraftVersion>(); + auto ver = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist.at(i)); if (ver->is_latest && !ver->is_snapshot) { return m_vlist.at(i); } } - return InstVersionPtr(); -} - -MinecraftVersionList &MinecraftVersionList::getMainList() -{ - return mcVList; + return BaseVersionPtr(); } -void MinecraftVersionList::updateListData(QList<InstVersionPtr > versions) +void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions) { beginResetModel(); m_vlist = versions; @@ -118,7 +107,6 @@ inline QDateTime timeFromS3Time(QString str) return QDateTime::fromString(str, Qt::ISODate); } - MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) { m_list = vlist; @@ -131,9 +119,13 @@ MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) legacyWhitelist.insert("1.4.6"); legacyWhitelist.insert("1.4.5"); legacyWhitelist.insert("1.4.4"); + legacyWhitelist.insert("1.4.3"); legacyWhitelist.insert("1.4.2"); + legacyWhitelist.insert("1.4.1"); + legacyWhitelist.insert("1.4"); legacyWhitelist.insert("1.3.2"); legacyWhitelist.insert("1.3.1"); + legacyWhitelist.insert("1.3"); legacyWhitelist.insert("1.2.5"); legacyWhitelist.insert("1.2.4"); legacyWhitelist.insert("1.2.3"); @@ -156,91 +148,91 @@ void MCVListLoadTask::executeTask() connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); } - void MCVListLoadTask::list_downloaded() { - if(vlistReply->error() != QNetworkReply::QNetworkReply::NoError) + if (vlistReply->error() != QNetworkReply::NoError) { vlistReply->deleteLater(); emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString()); return; } - + QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError); vlistReply->deleteLater(); - + if (jsonError.error != QJsonParseError::NoError) { emitFailed("Error parsing version list JSON:" + jsonError.errorString()); return; } - if(!jsonDoc.isObject()) + if (!jsonDoc.isObject()) { emitFailed("Error parsing version list JSON: jsonDoc is not an object"); return; } - + QJsonObject root = jsonDoc.object(); - + // Get the ID of the latest release and the latest snapshot. - if(!root.value("latest").isObject()) + if (!root.value("latest").isObject()) { emitFailed("Error parsing version list JSON: version list is missing 'latest' object"); return; } - + QJsonObject latest = root.value("latest").toObject(); - + QString latestReleaseID = latest.value("release").toString(""); QString latestSnapshotID = latest.value("snapshot").toString(""); - if(latestReleaseID.isEmpty()) + if (latestReleaseID.isEmpty()) { emitFailed("Error parsing version list JSON: latest release field is missing"); return; } - if(latestSnapshotID.isEmpty()) + if (latestSnapshotID.isEmpty()) { emitFailed("Error parsing version list JSON: latest snapshot field is missing"); return; } // Now, get the array of versions. - if(!root.value("versions").isArray()) + if (!root.value("versions").isArray()) { - emitFailed("Error parsing version list JSON: version list object is missing 'versions' array"); + emitFailed( + "Error parsing version list JSON: version list object is missing 'versions' array"); return; } QJsonArray versions = root.value("versions").toArray(); - - QList<InstVersionPtr > tempList; + + QList<BaseVersionPtr> tempList; for (int i = 0; i < versions.count(); i++) { bool is_snapshot = false; bool is_latest = false; - + // Load the version info. - if(!versions[i].isObject()) + if (!versions[i].isObject()) { - //FIXME: log this somewhere + // FIXME: log this somewhere continue; } QJsonObject version = versions[i].toObject(); QString versionID = version.value("id").toString(""); QString versionTimeStr = version.value("releaseTime").toString(""); QString versionTypeStr = version.value("type").toString(""); - if(versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) + if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) { - //FIXME: log this somewhere + // FIXME: log this somewhere continue; } - + // Parse the timestamp. QDateTime versionTime = timeFromS3Time(versionTimeStr); - if(!versionTime.isValid()) + if (!versionTime.isValid()) { - //FIXME: log this somewhere + // FIXME: log this somewhere continue; } // Parse the type. @@ -248,23 +240,25 @@ void MCVListLoadTask::list_downloaded() // OneSix or Legacy. use filter to determine type if (versionTypeStr == "release") { - versionType = legacyWhitelist.contains(versionID)?MinecraftVersion::Legacy:MinecraftVersion::OneSix; + versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy + : MinecraftVersion::OneSix; is_latest = (versionID == latestReleaseID); is_snapshot = false; } - else if(versionTypeStr == "snapshot") // It's a snapshot... yay + else if (versionTypeStr == "snapshot") // It's a snapshot... yay { - versionType = legacyWhitelist.contains(versionID)?MinecraftVersion::Legacy:MinecraftVersion::OneSix; + versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy + : MinecraftVersion::OneSix; is_latest = (versionID == latestSnapshotID); is_snapshot = true; } - else if(versionTypeStr == "old_alpha") + else if (versionTypeStr == "old_alpha") { versionType = MinecraftVersion::Nostalgia; is_latest = false; is_snapshot = false; } - else if(versionTypeStr == "old_beta") + else if (versionTypeStr == "old_beta") { versionType = MinecraftVersion::Legacy; is_latest = false; @@ -272,15 +266,15 @@ void MCVListLoadTask::list_downloaded() } else { - //FIXME: log this somewhere + // FIXME: log this somewhere continue; } // Get the download URL. QString dlUrl = QString(MCVLIST_URLBASE) + versionID + "/"; - + // Now, we construct the version object and add it to the list. - QSharedPointer<MinecraftVersion> mcVersion(new MinecraftVersion()); - mcVersion->name = mcVersion->descriptor = versionID; + std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); mcVersion->download_url = dlUrl; mcVersion->is_latest = is_latest; @@ -289,12 +283,7 @@ void MCVListLoadTask::list_downloaded() tempList.append(mcVersion); } m_list->updateListData(tempList); - + emitSucceeded(); return; } - -// FIXME: we should have a local cache of the version list and a local cache of version data -bool MCVListLoadTask::loadFromVList() -{ -} diff --git a/logic/lists/MinecraftVersionList.h b/logic/lists/MinecraftVersionList.h index 0477379f..ed68efbb 100644 --- a/logic/lists/MinecraftVersionList.h +++ b/logic/lists/MinecraftVersionList.h @@ -20,14 +20,14 @@ #include <QSet> #include <QSharedPointer> -#include "InstVersionList.h" +#include "BaseVersionList.h" #include "logic/tasks/Task.h" #include "logic/MinecraftVersion.h" class MCVListLoadTask; class QNetworkReply; -class MinecraftVersionList : public InstVersionList +class MinecraftVersionList : public BaseVersionList { Q_OBJECT public: @@ -37,25 +37,19 @@ public: virtual Task *getLoadTask(); virtual bool isLoaded(); - virtual const InstVersionPtr at(int i) const; + virtual const BaseVersionPtr at(int i) const; virtual int count() const; virtual void sort(); - virtual InstVersionPtr getLatestStable() const; - - /*! - * Gets the main version list instance. - */ - static MinecraftVersionList &getMainList(); - + virtual BaseVersionPtr getLatestStable() const; protected: - QList<InstVersionPtr > m_vlist; + QList<BaseVersionPtr> m_vlist; - bool m_loaded; + bool m_loaded = false; protected slots: - virtual void updateListData(QList<InstVersionPtr > versions); + virtual void updateListData(QList<BaseVersionPtr> versions); }; class MCVListLoadTask : public Task @@ -72,9 +66,6 @@ protected slots: void list_downloaded(); protected: - //! Loads versions from Mojang's official version list. - bool loadFromVList(); - QNetworkReply *vlistReply; MinecraftVersionList *m_list; MinecraftVersion *m_currentStable; diff --git a/logic/net/ByteArrayDownload.cpp b/logic/net/ByteArrayDownload.cpp new file mode 100644 index 00000000..ba771eef --- /dev/null +++ b/logic/net/ByteArrayDownload.cpp @@ -0,0 +1,65 @@ +#include "ByteArrayDownload.h" +#include "MultiMC.h" +#include <logger/QsLog.h> + +ByteArrayDownload::ByteArrayDownload(QUrl url) : Download() +{ + m_url = url; + m_status = Job_NotStarted; +} + +void ByteArrayDownload::start() +{ + QLOG_INFO() << "Downloading " << m_url.toString(); + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); + auto worker = MMC->qnam(); + QNetworkReply *rep = worker->get(request); + + m_reply = std::shared_ptr<QNetworkReply>(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), + SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); +} + +void ByteArrayDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit progress(index_within_job, bytesReceived, bytesTotal); +} + +void ByteArrayDownload::downloadError(QNetworkReply::NetworkError error) +{ + // error happened during download. + QLOG_ERROR() << "Error getting URL:" << m_url.toString().toLocal8Bit() + << "Network error: " << error; + m_status = Job_Failed; +} + +void ByteArrayDownload::downloadFinished() +{ + // if the download succeeded + if (m_status != Job_Failed) + { + // nothing went wrong... + m_status = Job_Finished; + m_data = m_reply->readAll(); + m_reply.reset(); + emit succeeded(index_within_job); + return; + } + // else the download failed + else + { + m_reply.reset(); + emit failed(index_within_job); + return; + } +} + +void ByteArrayDownload::downloadReadyRead() +{ + // ~_~ +} diff --git a/logic/net/ByteArrayDownload.h b/logic/net/ByteArrayDownload.h new file mode 100644 index 00000000..cfc6a8d0 --- /dev/null +++ b/logic/net/ByteArrayDownload.h @@ -0,0 +1,24 @@ +#pragma once +#include "Download.h" + +class ByteArrayDownload: public Download +{ + Q_OBJECT +public: + ByteArrayDownload(QUrl url); + +public: + /// if not saving to file, downloaded data is placed here + QByteArray m_data; + +public slots: + virtual void start(); + +protected slots: + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadError(QNetworkReply::NetworkError error); + void downloadFinished(); + void downloadReadyRead(); +}; + +typedef std::shared_ptr<ByteArrayDownload> ByteArrayDownloadPtr; diff --git a/logic/net/CacheDownload.cpp b/logic/net/CacheDownload.cpp new file mode 100644 index 00000000..309eb345 --- /dev/null +++ b/logic/net/CacheDownload.cpp @@ -0,0 +1,136 @@ +#include "MultiMC.h" +#include "CacheDownload.h" +#include <pathutils.h> + +#include <QCryptographicHash> +#include <QFileInfo> +#include <QDateTime> +#include <logger/QsLog.h> + +CacheDownload::CacheDownload(QUrl url, MetaEntryPtr entry) + : Download(), md5sum(QCryptographicHash::Md5) +{ + m_url = url; + m_entry = entry; + m_target_path = entry->getFullPath(); + m_status = Job_NotStarted; + m_opened_for_saving = false; +} + +void CacheDownload::start() +{ + if (!m_entry->stale) + { + emit succeeded(index_within_job); + return; + } + m_output_file.setFileName(m_target_path); + // if there already is a file and md5 checking is in effect and it can be opened + if (!ensureFilePathExists(m_target_path)) + { + emit failed(index_within_job); + return; + } + QLOG_INFO() << "Downloading " << m_url.toString(); + QNetworkRequest request(m_url); + if(m_entry->remote_changed_timestamp.size()) + request.setRawHeader(QString("If-Modified-Since").toLatin1(), m_entry->remote_changed_timestamp.toLatin1()); + if(m_entry->etag.size()) + request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->etag.toLatin1()); + + request.setHeader(QNetworkRequest::UserAgentHeader,"MultiMC/5.0 (Cached)"); + + auto worker = MMC->qnam(); + QNetworkReply *rep = worker->get(request); + + m_reply = std::shared_ptr<QNetworkReply>(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), + SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); +} + +void CacheDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit progress(index_within_job, bytesReceived, bytesTotal); +} + +void CacheDownload::downloadError(QNetworkReply::NetworkError error) +{ + // error happened during download. + QLOG_ERROR() << "Failed" << m_url.toString() << "with reason" << error; + m_status = Job_Failed; +} +void CacheDownload::downloadFinished() +{ + // if the download succeeded + if (m_status != Job_Failed) + { + + // nothing went wrong... + m_status = Job_Finished; + if (m_opened_for_saving) + { + // save the data to the downloadable if we aren't saving to file + m_output_file.close(); + m_entry->md5sum = md5sum.result().toHex().constData(); + } + else + { + if (m_output_file.open(QIODevice::ReadOnly)) + { + m_entry->md5sum = + QCryptographicHash::hash(m_output_file.readAll(), QCryptographicHash::Md5) + .toHex() + .constData(); + m_output_file.close(); + } + } + QFileInfo output_file_info(m_target_path); + + m_entry->etag = m_reply->rawHeader("ETag").constData(); + if(m_reply->hasRawHeader("Last-Modified")) + { + m_entry->remote_changed_timestamp = m_reply->rawHeader("Last-Modified").constData(); + } + m_entry->local_changed_timestamp = + output_file_info.lastModified().toUTC().toMSecsSinceEpoch(); + m_entry->stale = false; + MMC->metacache()->updateEntry(m_entry); + + m_reply.reset(); + emit succeeded(index_within_job); + return; + } + // else the download failed + else + { + m_output_file.close(); + m_output_file.remove(); + m_reply.reset(); + emit failed(index_within_job); + return; + } +} + +void CacheDownload::downloadReadyRead() +{ + if (!m_opened_for_saving) + { + if (!m_output_file.open(QIODevice::WriteOnly)) + { + /* + * Can't open the file... the job failed + */ + m_reply->abort(); + emit failed(index_within_job); + return; + } + m_opened_for_saving = true; + } + QByteArray ba = m_reply->readAll(); + md5sum.addData(ba); + m_output_file.write(ba); +} diff --git a/logic/net/CacheDownload.h b/logic/net/CacheDownload.h new file mode 100644 index 00000000..295391b1 --- /dev/null +++ b/logic/net/CacheDownload.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Download.h" +#include "HttpMetaCache.h" +#include <QFile> +#include <qcryptographichash.h> + +class CacheDownload : public Download +{ + Q_OBJECT +public: + MetaEntryPtr m_entry; + /// is the saving file already open? + bool m_opened_for_saving; + /// if saving to file, use the one specified in this string + QString m_target_path; + /// this is the output file, if any + QFile m_output_file; + /// the hash-as-you-download + QCryptographicHash md5sum; +public: + explicit CacheDownload(QUrl url, MetaEntryPtr entry); + +protected slots: + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead(); + +public slots: + virtual void start(); +}; + +typedef std::shared_ptr<CacheDownload> CacheDownloadPtr; diff --git a/logic/net/Download.h b/logic/net/Download.h new file mode 100644 index 00000000..ca4bee9f --- /dev/null +++ b/logic/net/Download.h @@ -0,0 +1,54 @@ +#pragma once + +#include <QObject> +#include <QUrl> +#include <memory> +#include <QNetworkReply> + +enum JobStatus +{ + Job_NotStarted, + Job_InProgress, + Job_Finished, + Job_Failed +}; + +class Download : public QObject +{ + Q_OBJECT +protected: + explicit Download() : QObject(0) {}; + +public: + virtual ~Download() {}; + +public: + /// the network reply + std::shared_ptr<QNetworkReply> m_reply; + + /// source URL + QUrl m_url; + + /// The file's status + JobStatus m_status; + + /// index within the parent job + int index_within_job = 0; + +signals: + void started(int index); + void progress(int index, qint64 current, qint64 total); + void succeeded(int index); + void failed(int index); + +protected slots: + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; + virtual void downloadError(QNetworkReply::NetworkError error) = 0; + virtual void downloadFinished() = 0; + virtual void downloadReadyRead() = 0; + +public slots: + virtual void start() = 0; +}; + +typedef std::shared_ptr<Download> DownloadPtr; diff --git a/logic/net/DownloadJob.cpp b/logic/net/DownloadJob.cpp index cad9ae72..fa3e655e 100644 --- a/logic/net/DownloadJob.cpp +++ b/logic/net/DownloadJob.cpp @@ -1,181 +1,135 @@ #include "DownloadJob.h" #include "pathutils.h" #include "MultiMC.h" +#include "FileDownload.h" +#include "ByteArrayDownload.h" +#include "CacheDownload.h" -Download::Download (QUrl url, QString target_path, QString expected_md5 ) - :Job() -{ - m_url = url; - m_target_path = target_path; - m_expected_md5 = expected_md5; - - m_check_md5 = m_expected_md5.size(); - m_save_to_file = m_target_path.size(); - m_status = Job_NotStarted; - m_opened_for_saving = false; -} - -void Download::start() -{ - if ( m_save_to_file ) - { - QString filename = m_target_path; - m_output_file.setFileName ( filename ); - // if there already is a file and md5 checking is in effect and it can be opened - if ( m_output_file.exists() && m_output_file.open ( QIODevice::ReadOnly ) ) - { - // check the md5 against the expected one - QString hash = QCryptographicHash::hash ( m_output_file.readAll(), QCryptographicHash::Md5 ).toHex().constData(); - m_output_file.close(); - // skip this file if they match - if ( m_check_md5 && hash == m_expected_md5 ) - { - qDebug() << "Skipping " << m_url.toString() << ": md5 match."; - emit succeeded(index_within_job); - return; - } - else - { - m_expected_md5 = hash; - } - } - if(!ensureFilePathExists(filename)) - { - emit failed(index_within_job); - return; - } - } - qDebug() << "Downloading " << m_url.toString(); - QNetworkRequest request ( m_url ); - request.setRawHeader(QString("If-None-Match").toLatin1(), m_expected_md5.toLatin1()); - - auto worker = MMC->qnam(); - QNetworkReply * rep = worker->get ( request ); - - m_reply = QSharedPointer<QNetworkReply> ( rep, &QObject::deleteLater ); - connect ( rep, SIGNAL ( downloadProgress ( qint64,qint64 ) ), SLOT ( downloadProgress ( qint64,qint64 ) ) ); - connect ( rep, SIGNAL ( finished() ), SLOT ( downloadFinished() ) ); - connect ( rep, SIGNAL ( error ( QNetworkReply::NetworkError ) ), SLOT ( downloadError ( QNetworkReply::NetworkError ) ) ); - connect ( rep, SIGNAL ( readyRead() ), SLOT ( downloadReadyRead() ) ); -} - -void Download::downloadProgress ( qint64 bytesReceived, qint64 bytesTotal ) -{ - emit progress (index_within_job, bytesReceived, bytesTotal ); -} +#include <logger/QsLog.h> -void Download::downloadError ( QNetworkReply::NetworkError error ) +ByteArrayDownloadPtr DownloadJob::addByteArrayDownload(QUrl url) { - // error happened during download. - // TODO: log the reason why - m_status = Job_Failed; + ByteArrayDownloadPtr ptr(new ByteArrayDownload(url)); + ptr->index_within_job = downloads.size(); + downloads.append(ptr); + parts_progress.append(part_info()); + total_progress++; + return ptr; } -void Download::downloadFinished() +FileDownloadPtr DownloadJob::addFileDownload(QUrl url, QString rel_target_path) { - // if the download succeeded - if ( m_status != Job_Failed ) - { - // nothing went wrong... - m_status = Job_Finished; - // save the data to the downloadable if we aren't saving to file - if ( !m_save_to_file ) - { - m_data = m_reply->readAll(); - } - else - { - m_output_file.close(); - } - - //TODO: check md5 here! - m_reply.clear(); - emit succeeded(index_within_job); - return; - } - // else the download failed - else - { - if ( m_save_to_file ) - { - m_output_file.close(); - m_output_file.remove(); - } - m_reply.clear(); - emit failed(index_within_job); - return; - } + FileDownloadPtr ptr(new FileDownload(url, rel_target_path)); + ptr->index_within_job = downloads.size(); + downloads.append(ptr); + parts_progress.append(part_info()); + total_progress++; + return ptr; } -void Download::downloadReadyRead() +CacheDownloadPtr DownloadJob::addCacheDownload(QUrl url, MetaEntryPtr entry) { - if( m_save_to_file ) - { - if(!m_opened_for_saving) - { - if ( !m_output_file.open ( QIODevice::WriteOnly ) ) - { - /* - * Can't open the file... the job failed - */ - m_reply->abort(); - emit failed(index_within_job); - return; - } - m_opened_for_saving = true; - } - m_output_file.write ( m_reply->readAll() ); - } + CacheDownloadPtr ptr(new CacheDownload(url, entry)); + ptr->index_within_job = downloads.size(); + downloads.append(ptr); + parts_progress.append(part_info()); + total_progress++; + return ptr; } -DownloadPtr DownloadJob::add ( QUrl url, QString rel_target_path, QString expected_md5 ) +ForgeXzDownloadPtr DownloadJob::addForgeXzDownload(QUrl url, MetaEntryPtr entry) { - DownloadPtr ptr (new Download(url, rel_target_path, expected_md5)); + ForgeXzDownloadPtr ptr(new ForgeXzDownload(url, entry)); ptr->index_within_job = downloads.size(); downloads.append(ptr); + parts_progress.append(part_info()); + total_progress++; return ptr; } -void DownloadJob::partSucceeded ( int index ) +void DownloadJob::partSucceeded(int index) { + // do progress. all slots are 1 in size at least + auto &slot = parts_progress[index]; + partProgress(index, slot.total_progress, slot.total_progress); + num_succeeded++; - if(num_failed + num_succeeded == downloads.size()) + QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_succeeded << "/" + << downloads.size(); + if (num_failed + num_succeeded == downloads.size()) { - if(num_failed) + if (num_failed) { - qDebug() << "Download JOB failed: " << this; + QLOG_ERROR() << m_job_name.toLocal8Bit() << "failed."; emit failed(); } else { - qDebug() << "Download JOB succeeded: " << this; + QLOG_INFO() << m_job_name.toLocal8Bit() << "succeeded."; emit succeeded(); } } } -void DownloadJob::partFailed ( int index ) +void DownloadJob::partFailed(int index) { - num_failed++; - if(num_failed + num_succeeded == downloads.size()) + auto &slot = parts_progress[index]; + if (slot.failures == 3) { - qDebug() << "Download JOB failed: " << this; - emit failed(); + QLOG_ERROR() << "Part" << index << "failed 3 times (" << downloads[index]->m_url << ")"; + num_failed++; + if (num_failed + num_succeeded == downloads.size()) + { + QLOG_ERROR() << m_job_name.toLocal8Bit() << "failed."; + emit failed(); + } + } + else + { + QLOG_ERROR() << "Part" << index << "failed, restarting (" << downloads[index]->m_url + << ")"; + // restart the job + slot.failures++; + downloads[index]->start(); } } -void DownloadJob::partProgress ( int index, qint64 bytesReceived, qint64 bytesTotal ) +void DownloadJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) { - // PROGRESS? DENIED! -} + auto &slot = parts_progress[index]; + + current_progress -= slot.current_progress; + slot.current_progress = bytesReceived; + current_progress += slot.current_progress; + total_progress -= slot.total_progress; + slot.total_progress = bytesTotal; + total_progress += slot.total_progress; + emit progress(current_progress, total_progress); +} void DownloadJob::start() { - qDebug() << "Download JOB started: " << this; - for(auto iter: downloads) + QLOG_INFO() << m_job_name.toLocal8Bit() << " started."; + for (auto iter : downloads) { - connect(iter.data(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(iter.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(iter.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(iter.get(), SIGNAL(progress(int, qint64, qint64)), + SLOT(partProgress(int, qint64, qint64))); iter->start(); } } + +QStringList DownloadJob::getFailedFiles() +{ + QStringList failed; + for (auto download : downloads) + { + if (download->m_status == Job_Failed) + { + failed.push_back(download->m_url.toString()); + } + } + return failed; +} diff --git a/logic/net/DownloadJob.h b/logic/net/DownloadJob.h index adeef646..91b014ad 100644 --- a/logic/net/DownloadJob.h +++ b/logic/net/DownloadJob.h @@ -1,98 +1,31 @@ #pragma once #include <QtNetwork> +#include "Download.h" +#include "ByteArrayDownload.h" +#include "FileDownload.h" +#include "CacheDownload.h" +#include "HttpMetaCache.h" +#include "ForgeXzDownload.h" +#include "logic/tasks/ProgressProvider.h" class DownloadJob; -class Download; -typedef QSharedPointer<DownloadJob> DownloadJobPtr; -typedef QSharedPointer<Download> DownloadPtr; - -enum JobStatus -{ - Job_NotStarted, - Job_InProgress, - Job_Finished, - Job_Failed -}; - -class Job : public QObject -{ - Q_OBJECT -protected: - explicit Job(): QObject(0){}; -public: - virtual ~Job() {}; - -public slots: - virtual void start() = 0; -}; - -class Download: public Job -{ - friend class DownloadJob; - Q_OBJECT -protected: - Download(QUrl url, QString rel_target_path = QString(), QString expected_md5 = QString()); -public: - /// the network reply - QSharedPointer<QNetworkReply> m_reply; - /// source URL - QUrl m_url; - - /// if true, check the md5sum against a provided md5sum - /// also, if a file exists, perform an md5sum first and don't download only if they don't match - bool m_check_md5; - /// the expected md5 checksum - QString m_expected_md5; - - /// save to file? - bool m_save_to_file; - /// is the saving file already open? - bool m_opened_for_saving; - /// if saving to file, use the one specified in this string - QString m_target_path; - /// this is the output file, if any - QFile m_output_file; - /// if not saving to file, downloaded data is placed here - QByteArray m_data; - - int currentProgress = 0; - int totalProgress = 0; - - /// The file's status - JobStatus m_status; - - int index_within_job = 0; -signals: - void started(int index); - void progress(int index, qint64 current, qint64 total); - void succeeded(int index); - void failed(int index); -public slots: - virtual void start(); - -private slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);; - void downloadError(QNetworkReply::NetworkError error); - void downloadFinished(); - void downloadReadyRead(); -}; +typedef std::shared_ptr<DownloadJob> DownloadJobPtr; /** * A single file for the downloader/cache to process. */ -class DownloadJob : public Job +class DownloadJob : public ProgressProvider { Q_OBJECT public: - explicit DownloadJob() - :Job(){}; - explicit DownloadJob(QUrl url, QString rel_target_path = QString(), QString expected_md5 = QString()) - :Job() - { - add(url, rel_target_path, expected_md5); - }; + explicit DownloadJob(QString job_name) + :ProgressProvider(), m_job_name(job_name){}; + + ByteArrayDownloadPtr addByteArrayDownload(QUrl url); + FileDownloadPtr addFileDownload(QUrl url, QString rel_target_path); + CacheDownloadPtr addCacheDownload(QUrl url, MetaEntryPtr entry); + ForgeXzDownloadPtr addForgeXzDownload(QUrl url, MetaEntryPtr entry); - DownloadPtr add(QUrl url, QString rel_target_path = QString(), QString expected_md5 = QString()); DownloadPtr operator[](int index) { return downloads[index]; @@ -107,6 +40,20 @@ public: { return downloads.size(); } + virtual void getProgress(qint64& current, qint64& total) + { + current = current_progress; + total = total_progress; + }; + virtual QString getStatus() const + { + return m_job_name; + }; + virtual bool isRunning() const + { + return m_running; + }; + QStringList getFailedFiles(); signals: void started(); void progress(qint64 current, qint64 total); @@ -119,8 +66,19 @@ private slots: void partSucceeded(int index); void partFailed(int index); private: + struct part_info + { + qint64 current_progress = 0; + qint64 total_progress = 1; + int failures = 0; + }; + QString m_job_name; QList<DownloadPtr> downloads; + QList<part_info> parts_progress; + qint64 current_progress = 0; + qint64 total_progress = 0; int num_succeeded = 0; int num_failed = 0; + bool m_running = false; }; diff --git a/logic/net/FileDownload.cpp b/logic/net/FileDownload.cpp new file mode 100644 index 00000000..3f38b0fa --- /dev/null +++ b/logic/net/FileDownload.cpp @@ -0,0 +1,112 @@ +#include "MultiMC.h" +#include "FileDownload.h" +#include <pathutils.h> +#include <QCryptographicHash> +#include <logger/QsLog.h> + + +FileDownload::FileDownload ( QUrl url, QString target_path ) + :Download() +{ + m_url = url; + m_target_path = target_path; + m_check_md5 = false; + m_status = Job_NotStarted; + m_opened_for_saving = false; +} + +void FileDownload::start() +{ + QString filename = m_target_path; + m_output_file.setFileName ( filename ); + // if there already is a file and md5 checking is in effect and it can be opened + if ( m_output_file.exists() && m_output_file.open ( QIODevice::ReadOnly ) ) + { + // check the md5 against the expected one + QString hash = QCryptographicHash::hash ( m_output_file.readAll(), QCryptographicHash::Md5 ).toHex().constData(); + m_output_file.close(); + // skip this file if they match + if ( m_check_md5 && hash == m_expected_md5 ) + { + QLOG_INFO() << "Skipping " << m_url.toString() << ": md5 match."; + emit succeeded(index_within_job); + return; + } + else + { + m_expected_md5 = hash; + } + } + if(!ensureFilePathExists(filename)) + { + emit failed(index_within_job); + return; + } + + QLOG_INFO() << "Downloading " << m_url.toString(); + QNetworkRequest request ( m_url ); + request.setRawHeader(QString("If-None-Match").toLatin1(), m_expected_md5.toLatin1()); + request.setHeader(QNetworkRequest::UserAgentHeader,"MultiMC/5.0 (Uncached)"); + + auto worker = MMC->qnam(); + QNetworkReply * rep = worker->get ( request ); + + m_reply = std::shared_ptr<QNetworkReply> ( rep ); + connect ( rep, SIGNAL ( downloadProgress ( qint64,qint64 ) ), SLOT ( downloadProgress ( qint64,qint64 ) ) ); + connect ( rep, SIGNAL ( finished() ), SLOT ( downloadFinished() ) ); + connect ( rep, SIGNAL ( error ( QNetworkReply::NetworkError ) ), SLOT ( downloadError ( QNetworkReply::NetworkError ) ) ); + connect ( rep, SIGNAL ( readyRead() ), SLOT ( downloadReadyRead() ) ); +} + +void FileDownload::downloadProgress ( qint64 bytesReceived, qint64 bytesTotal ) +{ + emit progress (index_within_job, bytesReceived, bytesTotal ); +} + +void FileDownload::downloadError ( QNetworkReply::NetworkError error ) +{ + // error happened during download. + // TODO: log the reason why + m_status = Job_Failed; +} + +void FileDownload::downloadFinished() +{ + // if the download succeeded + if ( m_status != Job_Failed ) + { + // nothing went wrong... + m_status = Job_Finished; + m_output_file.close(); + + m_reply.reset(); + emit succeeded(index_within_job); + return; + } + // else the download failed + else + { + m_output_file.close(); + m_reply.reset(); + emit failed(index_within_job); + return; + } +} + +void FileDownload::downloadReadyRead() +{ + if(!m_opened_for_saving) + { + if ( !m_output_file.open ( QIODevice::WriteOnly ) ) + { + /* + * Can't open the file... the job failed + */ + m_reply->abort(); + emit failed(index_within_job); + return; + } + m_opened_for_saving = true; + } + m_output_file.write ( m_reply->readAll() ); +} diff --git a/logic/net/FileDownload.h b/logic/net/FileDownload.h new file mode 100644 index 00000000..9abb590d --- /dev/null +++ b/logic/net/FileDownload.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Download.h" +#include <QFile> + +class FileDownload : public Download +{ + Q_OBJECT +public: + /// if true, check the md5sum against a provided md5sum + /// also, if a file exists, perform an md5sum first and don't download only if they don't match + bool m_check_md5; + /// the expected md5 checksum + QString m_expected_md5; + /// is the saving file already open? + bool m_opened_for_saving; + /// if saving to file, use the one specified in this string + QString m_target_path; + /// this is the output file, if any + QFile m_output_file; + +public: + explicit FileDownload(QUrl url, QString target_path); + +protected slots: + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead(); + +public slots: + virtual void start(); +}; + +typedef std::shared_ptr<FileDownload> FileDownloadPtr; diff --git a/logic/net/ForgeXzDownload.cpp b/logic/net/ForgeXzDownload.cpp new file mode 100644 index 00000000..0e5287d8 --- /dev/null +++ b/logic/net/ForgeXzDownload.cpp @@ -0,0 +1,279 @@ +#include "MultiMC.h" +#include "ForgeXzDownload.h" +#include <pathutils.h> + +#include <QCryptographicHash> +#include <QFileInfo> +#include <QDateTime> +#include <logger/QsLog.h> + +ForgeXzDownload::ForgeXzDownload(QUrl url, MetaEntryPtr entry) + : Download() +{ + QString urlstr = url.toString(); + urlstr.append(".pack.xz"); + m_url = QUrl(urlstr); + m_entry = entry; + m_target_path = entry->getFullPath(); + m_status = Job_NotStarted; + m_opened_for_saving = false; +} + +void ForgeXzDownload::start() +{ + if (!m_entry->stale) + { + emit succeeded(index_within_job); + return; + } + // can we actually create the real, final file? + if (!ensureFilePathExists(m_target_path)) + { + emit failed(index_within_job); + return; + } + QLOG_INFO() << "Downloading " << m_url.toString(); + QNetworkRequest request(m_url); + request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->etag.toLatin1()); + request.setHeader(QNetworkRequest::UserAgentHeader,"MultiMC/5.0 (Cached)"); + + auto worker = MMC->qnam(); + QNetworkReply *rep = worker->get(request); + + m_reply = std::shared_ptr<QNetworkReply>(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), + SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); +} + +void ForgeXzDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit progress(index_within_job, bytesReceived, bytesTotal); +} + +void ForgeXzDownload::downloadError(QNetworkReply::NetworkError error) +{ + // error happened during download. + // TODO: log the reason why + m_status = Job_Failed; +} + +void ForgeXzDownload::downloadFinished() +{ + // if the download succeeded + if (m_status != Job_Failed) + { + // nothing went wrong... + m_status = Job_Finished; + if (m_opened_for_saving) + { + // we actually downloaded something! process and isntall it + decompressAndInstall(); + return; + } + else + { + // something bad happened + m_pack200_xz_file.remove(); + m_reply.reset(); + emit failed(index_within_job); + return; + } + } + // else the download failed + else + { + m_pack200_xz_file.close(); + m_pack200_xz_file.remove(); + m_reply.reset(); + emit failed(index_within_job); + return; + } +} + +void ForgeXzDownload::downloadReadyRead() +{ + + if (!m_opened_for_saving) + { + if (!m_pack200_xz_file.open()) + { + /* + * Can't open the file... the job failed + */ + m_reply->abort(); + emit failed(index_within_job); + return; + } + m_opened_for_saving = true; + } + m_pack200_xz_file.write(m_reply->readAll()); +} + +#include "xz.h" +#include "unpack200.h" +#include <stdexcept> + +const size_t buffer_size = 8196; + +void ForgeXzDownload::decompressAndInstall() +{ + // rewind the downloaded temp file + m_pack200_xz_file.seek(0); + // de-xz'd file + QTemporaryFile pack200_file; + pack200_file.open(); + + bool xz_success = false; + // first, de-xz + { + uint8_t in[buffer_size]; + uint8_t out[buffer_size]; + struct xz_buf b; + struct xz_dec *s; + enum xz_ret ret; + xz_crc32_init(); + xz_crc64_init(); + s = xz_dec_init(XZ_DYNALLOC, 1 << 26); + if (s == nullptr) + { + xz_dec_end(s); + emit failed(index_within_job); + return; + } + b.in = in; + b.in_pos = 0; + b.in_size = 0; + b.out = out; + b.out_pos = 0; + b.out_size = buffer_size; + while (!xz_success) + { + if (b.in_pos == b.in_size) + { + b.in_size = m_pack200_xz_file.read((char*)in, sizeof(in)); + b.in_pos = 0; + } + + ret = xz_dec_run(s, &b); + + if (b.out_pos == sizeof(out)) + { + if (pack200_file.write((char*)out, b.out_pos) != b.out_pos) + { + // msg = "Write error\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + } + + b.out_pos = 0; + } + + if (ret == XZ_OK) + continue; + + if (ret == XZ_UNSUPPORTED_CHECK) + { + // unsupported check. this is OK, but we should log this + continue; + } + + if (pack200_file.write((char*)out, b.out_pos) != b.out_pos ) + { + // write error + pack200_file.close(); + xz_dec_end(s); + return; + } + + switch (ret) + { + case XZ_STREAM_END: + xz_dec_end(s); + xz_success = true; + break; + + case XZ_MEM_ERROR: + QLOG_ERROR() << "Memory allocation failed\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + case XZ_MEMLIMIT_ERROR: + QLOG_ERROR() << "Memory usage limit reached\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + case XZ_FORMAT_ERROR: + QLOG_ERROR() << "Not a .xz file\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + case XZ_OPTIONS_ERROR: + QLOG_ERROR() << "Unsupported options in the .xz headers\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + case XZ_DATA_ERROR: + case XZ_BUF_ERROR: + QLOG_ERROR() << "File is corrupt\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + default: + QLOG_ERROR() << "Bug!\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + } + } + } + + // revert pack200 + pack200_file.close(); + QString pack_name = pack200_file.fileName(); + try + { + unpack_200(pack_name.toStdString(), m_target_path.toStdString()); + } + catch(std::runtime_error & err) + { + QLOG_ERROR() << "Error unpacking " << pack_name.toUtf8() << " : " << err.what(); + QFile f(m_target_path); + if(f.exists()) + f.remove(); + emit failed(index_within_job); + return; + } + + QFile jar_file(m_target_path); + + if (!jar_file.open(QIODevice::ReadOnly)) + { + jar_file.remove(); + emit failed(index_within_job); + return; + } + m_entry->md5sum = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5) + .toHex() + .constData(); + jar_file.close(); + + QFileInfo output_file_info(m_target_path); + m_entry->etag = m_reply->rawHeader("ETag").constData(); + m_entry->local_changed_timestamp = + output_file_info.lastModified().toUTC().toMSecsSinceEpoch(); + m_entry->stale = false; + MMC->metacache()->updateEntry(m_entry); + + m_reply.reset(); + emit succeeded(index_within_job); +} diff --git a/logic/net/ForgeXzDownload.h b/logic/net/ForgeXzDownload.h new file mode 100644 index 00000000..5d677947 --- /dev/null +++ b/logic/net/ForgeXzDownload.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Download.h" +#include "HttpMetaCache.h" +#include <QFile> +#include <QTemporaryFile> + +class ForgeXzDownload : public Download +{ + Q_OBJECT +public: + MetaEntryPtr m_entry; + /// is the saving file already open? + bool m_opened_for_saving; + /// if saving to file, use the one specified in this string + QString m_target_path; + /// this is the output file, if any + QTemporaryFile m_pack200_xz_file; + +public: + explicit ForgeXzDownload(QUrl url, MetaEntryPtr entry); + +protected slots: + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead(); + +public slots: + virtual void start(); +private: + void decompressAndInstall(); +}; + +typedef std::shared_ptr<ForgeXzDownload> ForgeXzDownloadPtr; diff --git a/logic/net/HttpMetaCache.cpp b/logic/net/HttpMetaCache.cpp index 87741dc9..5ba5b98d 100644 --- a/logic/net/HttpMetaCache.cpp +++ b/logic/net/HttpMetaCache.cpp @@ -1,38 +1,130 @@ +#include "MultiMC.h" #include "HttpMetaCache.h" #include <pathutils.h> + +#include <QFileInfo> #include <QFile> -#include <qjsondocument.h> -#include <qjsonarray.h> -#include <qjsonobject.h> -#include <qfileinfo.h> -#include <qtemporaryfile.h> -#include <qsavefile.h> +#include <QTemporaryFile> +#include <QSaveFile> +#include <QDateTime> +#include <QCryptographicHash> + +#include <logger/QsLog.h> + +#include <QJsonDocument> +#include <QJsonArray> +#include <QJsonObject> + +QString MetaEntry::getFullPath() +{ + return PathCombine(MMC->metacache()->getBasePath(base), path); +} + HttpMetaCache::HttpMetaCache(QString path) + :QObject() { m_index_file = path; + saveBatchingTimer.setSingleShot(true); + saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); + connect(&saveBatchingTimer,SIGNAL(timeout()),SLOT(SaveNow())); } + HttpMetaCache::~HttpMetaCache() { - Save(); + saveBatchingTimer.stop(); + SaveNow(); } -void HttpMetaCache::addEntry ( QString base, QString resource_path, QString etag ) +MetaEntryPtr HttpMetaCache::getEntry ( QString base, QString resource_path ) { // no base. no base path. can't store if(!m_entries.contains(base)) - return; - QString real_path = PathCombine(m_entries[base].base_path, resource_path); + { + // TODO: log problem + return MetaEntryPtr(); + } + EntryMap & map = m_entries[base]; + if(map.entry_list.contains(resource_path)) + { + return map.entry_list[resource_path]; + } + return MetaEntryPtr(); +} + +MetaEntryPtr HttpMetaCache::resolveEntry ( QString base, QString resource_path, QString expected_etag ) +{ + auto entry = getEntry(base, resource_path); + // it's not present? generate a default stale entry + if(!entry) + { + return staleEntry(base, resource_path); + } + + auto & selected_base = m_entries[base]; + QString real_path = PathCombine(selected_base.base_path, resource_path); QFileInfo finfo(real_path); - // just ignore it, it's garbage if it's not a proper file + // is the file really there? if not -> stale if(!finfo.isFile() || !finfo.isReadable()) { - // TODO: log problem - return; + // if the file doesn't exist, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + + if(!expected_etag.isEmpty() && expected_etag != entry->etag) + { + // if the etag doesn't match expected, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); } - Save(); + // if the file changed, check md5sum + qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); + if(file_last_changed != entry->local_changed_timestamp) + { + QFile input(real_path); + input.open(QIODevice::ReadOnly); + QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData(); + if(entry->md5sum != md5sum) + { + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + // md5sums matched... keep entry and save the new state to file + entry->local_changed_timestamp = file_last_changed; + SaveEventually(); + } + + // entry passed all the checks we cared about. + return entry; +} + +bool HttpMetaCache::updateEntry ( MetaEntryPtr stale_entry ) +{ + if(!m_entries.contains(stale_entry->base)) + { + QLOG_ERROR() << "Cannot add entry with unknown base: " << stale_entry->base.toLocal8Bit(); + return false; + } + if(stale_entry->stale) + { + QLOG_ERROR() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); + return false; + } + m_entries[stale_entry->base].entry_list[stale_entry->path] = stale_entry; + SaveEventually(); + return true; +} + +MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path) +{ + auto foo = new MetaEntry; + foo->base = base; + foo->path = resource_path; + foo->stale = true; + return MetaEntryPtr(foo); } void HttpMetaCache::addBase ( QString base, QString base_root ) @@ -46,6 +138,16 @@ void HttpMetaCache::addBase ( QString base, QString base_root ) m_entries[base] = foo; } +QString HttpMetaCache::getBasePath ( QString base ) +{ + if(m_entries.contains(base)) + { + return m_entries[base].base_path; + } + return QString(); +} + + void HttpMetaCache::Load() { QFile index(m_index_file); @@ -65,12 +167,12 @@ void HttpMetaCache::Load() // read the entry array auto entries_val =root.value("entries"); - if(!version_val.isArray()) + if(!entries_val.isArray()) return; - QJsonArray array = json.array(); + QJsonArray array = entries_val.toArray(); for(auto element: array) { - if(!element.isObject()); + if(!element.isObject()) return; auto element_obj = element.toObject(); QString base = element_obj.value("base").toString(); @@ -82,12 +184,22 @@ void HttpMetaCache::Load() QString path = foo->path = element_obj.value("path").toString(); foo->md5sum = element_obj.value("md5sum").toString(); foo->etag = element_obj.value("etag").toString(); - foo->last_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); + foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); + foo->remote_changed_timestamp = element_obj.value("remote_changed_timestamp").toString(); + // presumed innocent until closer examination + foo->stale = false; entrymap.entry_list[path] = MetaEntryPtr( foo ); } } -void HttpMetaCache::Save() +void HttpMetaCache::SaveEventually() +{ + // reset the save timer + saveBatchingTimer.stop(); + saveBatchingTimer.start(30000); +} + +void HttpMetaCache::SaveNow() { QSaveFile tfile(m_index_file); if(!tfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) @@ -104,7 +216,9 @@ void HttpMetaCache::Save() entryObj.insert("path", QJsonValue(entry->path)); entryObj.insert("md5sum", QJsonValue(entry->md5sum)); entryObj.insert("etag", QJsonValue(entry->etag)); - entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->last_changed_timestamp))); + entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp))); + if(!entry->remote_changed_timestamp.isEmpty()) + entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp)); entriesArr.append(entryObj); } } @@ -118,14 +232,3 @@ void HttpMetaCache::Save() return; tfile.commit(); } - - -MetaEntryPtr HttpMetaCache::getEntryForResource ( QString base, QString resource_path ) -{ - if(!m_entries.contains(base)) - return MetaEntryPtr(); - auto & entrymap = m_entries[base]; - if(!entrymap.entry_list.contains(resource_path)) - return MetaEntryPtr(); - return entrymap.entry_list[resource_path]; -} diff --git a/logic/net/HttpMetaCache.h b/logic/net/HttpMetaCache.h index 161483ad..557d9298 100644 --- a/logic/net/HttpMetaCache.h +++ b/logic/net/HttpMetaCache.h @@ -2,6 +2,7 @@ #include <QString> #include <QSharedPointer> #include <QMap> +#include <qtimer.h> struct MetaEntry { @@ -9,23 +10,46 @@ struct MetaEntry QString path; QString md5sum; QString etag; - quint64 last_changed_timestamp = 0; + qint64 local_changed_timestamp = 0; + QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time + bool stale = true; + QString getFullPath(); }; -typedef QSharedPointer<MetaEntry> MetaEntryPtr; +typedef std::shared_ptr<MetaEntry> MetaEntryPtr; -class HttpMetaCache +class HttpMetaCache : public QObject { + Q_OBJECT public: // supply path to the cache index file HttpMetaCache(QString path); ~HttpMetaCache(); - MetaEntryPtr getEntryForResource(QString base, QString resource_path); - void addEntry(QString base, QString resource_path, QString etag); + + // get the entry solely from the cache + // you probably don't want this, unless you have some specific caching needs. + MetaEntryPtr getEntry(QString base, QString resource_path); + + // get the entry from cache and verify that it isn't stale (within reason) + MetaEntryPtr resolveEntry(QString base, QString resource_path, + QString expected_etag = QString()); + + // add a previously resolved stale entry + bool updateEntry(MetaEntryPtr stale_entry); + void addBase(QString base, QString base_root); -private: - void Save(); + + // (re)start a timer that calls SaveNow later. + void SaveEventually(); void Load(); + QString getBasePath(QString base); +public +slots: + void SaveNow(); + +private: + // create a new stale entry, given the parameters + MetaEntryPtr staleEntry(QString base, QString resource_path); struct EntryMap { QString base_path; @@ -33,4 +57,5 @@ private: }; QMap<QString, EntryMap> m_entries; QString m_index_file; + QTimer saveBatchingTimer; };
\ No newline at end of file diff --git a/logic/net/LoginTask.cpp b/logic/net/LoginTask.cpp new file mode 100644 index 00000000..4098783b --- /dev/null +++ b/logic/net/LoginTask.cpp @@ -0,0 +1,285 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LoginTask.h" +#include "MultiMC.h" +#include <settingsobject.h> + +#include <QStringList> + +#include <QNetworkReply> +#include <QNetworkRequest> + +#include <QUrl> +#include <QUrlQuery> +#include <QJsonParseError> +#include <QJsonObject> + +LoginTask::LoginTask(const UserInfo &uInfo, QObject *parent) : Task(parent), uInfo(uInfo) +{ +} + +void LoginTask::executeTask() +{ + yggdrasilLogin(); +} + +void LoginTask::legacyLogin() +{ + setStatus(tr("Logging in...")); + auto worker = MMC->qnam(); + connect(worker.get(), SIGNAL(finished(QNetworkReply *)), this, + SLOT(processLegacyReply(QNetworkReply *))); + + QUrl loginURL("https://login.minecraft.net/"); + QNetworkRequest netRequest(loginURL); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, + "application/x-www-form-urlencoded"); + + QUrlQuery params; + params.addQueryItem("user", uInfo.username); + params.addQueryItem("password", uInfo.password); + params.addQueryItem("version", "13"); + + netReply = worker->post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); +} + +void LoginTask::parseLegacyReply(QByteArray data) +{ + QString responseStr = QString::fromUtf8(data); + + QStringList strings = responseStr.split(":"); + if (strings.count() >= 4) + { + // strings[1] is the download ticket. It isn't used anymore. + QString username = strings[2]; + QString sessionID = strings[3]; + /* + struct LoginResponse + { + QString username; + QString session_id; + QString player_name; + QString player_id; + QString client_id; + }; + */ + result = {username, sessionID, username, QString()}; + emitSucceeded(); + } + else + { + if (responseStr.toLower() == "bad login") + emitFailed(tr("Invalid username or password.")); + else if (responseStr.toLower() == "old version") + emitFailed(tr("Launcher outdated, please update.")); + else + emitFailed(tr("Login failed: %1").arg(responseStr)); + } +} + +void LoginTask::processLegacyReply(QNetworkReply *reply) +{ + processReply(reply, &LoginTask::parseLegacyReply, &LoginTask::parseLegacyError); +} + +void LoginTask::processYggdrasilReply(QNetworkReply *reply) +{ + processReply(reply, &LoginTask::parseYggdrasilReply, &LoginTask::parseYggdrasilError); +} + +void LoginTask::processReply(QNetworkReply *reply, std::function<void (LoginTask*, QByteArray)> parser, std::function<QString (LoginTask*, QNetworkReply*)> errorHandler) +{ + if (netReply != reply) + return; + // Check for errors. + switch (reply->error()) + { + case QNetworkReply::NoError: + { + // Check the response code. + int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + switch (responseCode) + { + case 200: + parser(this, reply->readAll()); + break; + + default: + emitFailed(tr("Login failed: Unknown HTTP code %1 encountered.").arg(responseCode)); + break; + } + + break; + } + + case QNetworkReply::OperationCanceledError: + emitFailed(tr("Login canceled.")); + break; + + default: + emitFailed(errorHandler(this, reply)); + break; + } +} + +QString LoginTask::parseLegacyError(QNetworkReply *reply) +{ + int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + switch (responseCode) + { + case 403: + return tr("Invalid username or password."); + + case 503: + return tr("The login servers are currently unavailable. Check " + "http://help.mojang.com/ for more info."); + + default: + QLOG_DEBUG() << "Login failed with QNetworkReply code:" << reply->error(); + return tr("Login failed: %1").arg(reply->errorString()); + } +} + +QString LoginTask::parseYggdrasilError(QNetworkReply *reply) +{ + QByteArray data = reply->readAll(); + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + // If there are JSON errors fall back to using the legacy error handling using HTTP status codes + if (jsonError.error != QJsonParseError::NoError) + { + return parseLegacyError(reply); + } + + if (!jsonDoc.isObject()) + { + return parseLegacyError(reply); + } + + QJsonObject root = jsonDoc.object(); + + //QString error = root.value("error").toString(); + QString errorMessage = root.value("errorMessage").toString(); + + if(errorMessage.isEmpty()) + { + return parseLegacyError(reply); + } + + return tr("Login failed: ") + errorMessage; +} + +void LoginTask::yggdrasilLogin() +{ + setStatus(tr("Logging in...")); + auto worker = MMC->qnam(); + connect(worker.get(), SIGNAL(finished(QNetworkReply *)), this, + SLOT(processYggdrasilReply(QNetworkReply *))); + + /* + { + // agent def. version might be incremented at some point + "agent":{"name":"Minecraft","version":1}, + "username": "mojang account name", + "password": "mojang account password", + // client token is optional. but we supply one anyway + "clientToken": "client identifier" + } + */ + + QUrl loginURL("https://authserver.mojang.com/authenticate"); + QNetworkRequest netRequest(loginURL); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + auto settings = MMC->settings(); + QString clientToken = settings->get("YggdrasilClientToken").toString(); + // escape the {} + clientToken.remove('{'); + clientToken.remove('}'); + // create the request + QString requestConstent; + requestConstent += "{"; + requestConstent += " \"agent\":{\"name\":\"Minecraft\",\"version\":1},\n"; + requestConstent += " \"username\":\"" + uInfo.username + "\",\n"; + requestConstent += " \"password\":\"" + uInfo.password + "\",\n"; + requestConstent += " \"clientToken\":\"" + clientToken + "\"\n"; + requestConstent += "}"; + netReply = worker->post(netRequest, requestConstent.toUtf8()); +} + +/* +{ + "accessToken": "random access token", // hexadecimal + "clientToken": "client identifier", // identical to the one received + "availableProfiles": [ // only present if the agent field was received + { + "id": "profile identifier", // hexadecimal + "name": "player name" + } + ], + "selectedProfile": { // only present if the agent field was received + "id": "profile identifier", + "name": "player name" + } +} +*/ +void LoginTask::parseYggdrasilReply(QByteArray data) +{ + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed(tr("Login failed: %1").arg(jsonError.errorString())); + return; + } + + if (!jsonDoc.isObject()) + { + emitFailed(tr("Login failed: BAD FORMAT #1")); + return; + } + + QJsonObject root = jsonDoc.object(); + + QString accessToken = root.value("accessToken").toString(); + QString clientToken = root.value("clientToken").toString(); + QString playerID; + QString playerName; + auto selectedProfile = root.value("selectedProfile"); + if(selectedProfile.isObject()) + { + auto selectedProfileO = selectedProfile.toObject(); + playerID = selectedProfileO.value("id").toString(); + playerName = selectedProfileO.value("name").toString(); + } + QString sessionID = "token:" + accessToken + ":" + playerID; + /* + struct LoginResponse + { + QString username; + QString session_id; + QString player_name; + QString player_id; + QString client_id; + }; + */ + + result = {uInfo.username, sessionID, playerName, playerID, accessToken}; + emitSucceeded(); +} diff --git a/logic/tasks/LoginTask.h b/logic/net/LoginTask.h index bde672b8..aa925999 100644 --- a/logic/tasks/LoginTask.h +++ b/logic/net/LoginTask.h @@ -3,7 +3,7 @@ * 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 @@ -13,10 +13,9 @@ * limitations under the License. */ -#ifndef LOGINTASK_H -#define LOGINTASK_H +#pragma once -#include "Task.h" +#include "logic/tasks/Task.h" #include <QSharedPointer> struct UserInfo @@ -28,8 +27,10 @@ struct UserInfo struct LoginResponse { QString username; - QString sessionID; - qint64 latestVersion; + QString session_id; // session id is a combination of player id and the access token + QString player_name; + QString player_id; + QString access_token; }; class QNetworkReply; @@ -38,21 +39,29 @@ class LoginTask : public Task { Q_OBJECT public: - explicit LoginTask(const UserInfo& uInfo, QObject *parent = 0); + explicit LoginTask(const UserInfo &uInfo, QObject *parent = 0); LoginResponse getResult() { return result; - }; - + } + protected slots: - void processNetReply(QNetworkReply* reply); - + void legacyLogin(); + void processLegacyReply(QNetworkReply *reply); + void parseLegacyReply(QByteArray data); + QString parseLegacyError(QNetworkReply *reply); + + void yggdrasilLogin(); + void processYggdrasilReply(QNetworkReply *reply); + void parseYggdrasilReply(QByteArray data); + QString parseYggdrasilError(QNetworkReply *reply); + + void processReply(QNetworkReply *reply, std::function<void(LoginTask*, QByteArray)>, std::function<QString(LoginTask*, QNetworkReply*)>); + protected: void executeTask(); - + LoginResponse result; - QNetworkReply* netReply; + QNetworkReply *netReply; UserInfo uInfo; }; - -#endif // LOGINTASK_H diff --git a/logic/tasks/LoginTask.cpp b/logic/tasks/LoginTask.cpp deleted file mode 100644 index ad9de7f5..00000000 --- a/logic/tasks/LoginTask.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "LoginTask.h" -#include "MultiMC.h" - -#include <QStringList> - -#include <QNetworkReply> -#include <QNetworkRequest> - -#include <QUrl> -#include <QUrlQuery> - -LoginTask::LoginTask( const UserInfo& uInfo, QObject* parent ) : Task(parent), uInfo(uInfo){} - -void LoginTask::executeTask() -{ - setStatus("Logging in..."); - auto worker = MMC->qnam(); - connect(worker, SIGNAL(finished(QNetworkReply*)), this, SLOT(processNetReply(QNetworkReply*))); - - QUrl loginURL("https://login.minecraft.net/"); - QNetworkRequest netRequest(loginURL); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QUrlQuery params; - params.addQueryItem("user", uInfo.username); - params.addQueryItem("password", uInfo.password); - params.addQueryItem("version", "13"); - - netReply = worker->post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); -} - -void LoginTask::processNetReply(QNetworkReply *reply) -{ - if(netReply != reply) - return; - // Check for errors. - switch (reply->error()) - { - case QNetworkReply::NoError: - { - // Check the response code. - int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) - { - QString responseStr(reply->readAll()); - - QStringList strings = responseStr.split(":"); - if (strings.count() >= 4) - { - bool parseSuccess; - qint64 latestVersion = strings[0].toLongLong(&parseSuccess); - if (parseSuccess) - { - // strings[1] is the download ticket. It isn't used anymore. - QString username = strings[2]; - QString sessionID = strings[3]; - - result = {username, sessionID, latestVersion}; - emitSucceeded(); - } - else - { - emitFailed("Failed to parse Minecraft version string."); - } - } - else - { - if (responseStr.toLower() == "bad login") - emitFailed("Invalid username or password."); - else if (responseStr.toLower() == "old version") - emitFailed("Launcher outdated, please update."); - else - emitFailed("Login failed: " + responseStr); - } - } - else if (responseCode == 503) - { - emitFailed("The login servers are currently unavailable. Check http://help.mojang.com/ for more info."); - } - else - { - emitFailed(QString("Login failed: Unknown HTTP error %1 occurred.").arg(QString::number(responseCode))); - } - break; - } - - case QNetworkReply::OperationCanceledError: - emitFailed("Login canceled."); - break; - - default: - emitFailed("Login failed: " + reply->errorString()); - break; - } -} diff --git a/logic/tasks/ProgressProvider.h b/logic/tasks/ProgressProvider.h new file mode 100644 index 00000000..e158eb54 --- /dev/null +++ b/logic/tasks/ProgressProvider.h @@ -0,0 +1,20 @@ +#pragma once +#include <QObject> +class ProgressProvider : public QObject +{ + Q_OBJECT +protected: + explicit ProgressProvider(QObject* parent = 0): QObject(parent){} +signals: + void started(); + void progress(qint64 current, qint64 total); + void succeeded(); + void failed(QString reason); + void status(QString status); +public: + virtual QString getStatus() const = 0; + virtual void getProgress(qint64 ¤t, qint64 &total) = 0; + virtual bool isRunning() const = 0; +public slots: + virtual void start() = 0; +}; diff --git a/logic/tasks/Task.cpp b/logic/tasks/Task.cpp index 7c148591..2f137c55 100644 --- a/logic/tasks/Task.cpp +++ b/logic/tasks/Task.cpp @@ -14,72 +14,60 @@ */ #include "Task.h" +#include <logger/QsLog.h> Task::Task(QObject *parent) : - QObject(parent) + ProgressProvider(parent) { } QString Task::getStatus() const { - return status; + return m_status; } -void Task::setStatus(const QString &status) +void Task::setStatus(const QString &new_status) { - this->status = status; - emitStatusChange(status); + m_status = new_status; + emit status(new_status); } -int Task::getProgress() const +void Task::setProgress(int new_progress) { - return progress; + m_progress = new_progress; + emit progress(new_progress, 100); } -void Task::setProgress(int progress) +void Task::getProgress(qint64& current, qint64& total) { - this->progress = progress; - emitProgressChange(progress); + current = m_progress; + total = 100; } -void Task::startTask() -{ - emitStarted(); - executeTask(); -} -void Task::emitStarted() +void Task::start() { - running = true; + m_running = true; emit started(); + executeTask(); } void Task::emitFailed(QString reason) { - running = false; + m_running = false; + QLOG_ERROR() << "Task failed: " << reason; emit failed(reason); } void Task::emitSucceeded() { - running = false; + m_running = false; emit succeeded(); } bool Task::isRunning() const { - return running; -} - - -void Task::emitStatusChange(const QString &status) -{ - emit statusChanged(status); -} - -void Task::emitProgressChange(int progress) -{ - emit progressChanged(progress); + return m_running; } diff --git a/logic/tasks/Task.h b/logic/tasks/Task.h index 91852b0f..cfe71c51 100644 --- a/logic/tasks/Task.h +++ b/logic/tasks/Task.h @@ -13,53 +13,37 @@ * limitations under the License. */ -#ifndef TASK_H -#define TASK_H +#pragma once #include <QObject> #include <QString> +#include "ProgressProvider.h" -class Task : public QObject +class Task : public ProgressProvider { Q_OBJECT public: explicit Task(QObject *parent = 0); - QString getStatus() const; - int getProgress() const; - bool isRunning() const; + virtual QString getStatus() const; + virtual void getProgress(qint64& current, qint64& total); + virtual bool isRunning() const; public slots: - void startTask(); - -protected slots: - void setStatus(const QString& status); - void setProgress(int progress); - -signals: - void started(); - void failed(QString reason); - void succeeded(); - - void statusChanged(Task* task, const QString& status); - void progressChanged(Task* task, int progress); - - void statusChanged(const QString& status); - void progressChanged(int progress); + virtual void start(); protected: virtual void executeTask() = 0; - virtual void emitStarted(); - virtual void emitFailed(QString reason); virtual void emitSucceeded(); + virtual void emitFailed(QString reason); + +protected slots: + void setStatus(const QString& status); + void setProgress(int progress); - virtual void emitStatusChange(const QString &status); - virtual void emitProgressChange(int progress); - - QString status; - int progress; - bool running = false; +protected: + QString m_status; + int m_progress = 0; + bool m_running = false; }; - -#endif // TASK_H diff --git a/package/linux/MultiMC b/package/linux/MultiMC index 235d17c5..6e77a632 100755 --- a/package/linux/MultiMC +++ b/package/linux/MultiMC @@ -1,9 +1,12 @@ #!/bin/sh # Basic start script for running MultiMC with the libs packaged with it. -MMC_DIR=$(dirname "$0") +MMC_DIR="$( cd "$( dirname "$0" )" && pwd )" cd "${MMC_DIR}" +echo "MultiMC Dir: ${MMC_DIR}" + +export LD_LIBRARY_PATH="${MMC_DIR}/bin":$LD_LIBRARY_PATH +export QT_PLUGIN_PATH="${MMC_DIR}/plugins" +export QT_FONTPATH="${MMC_DIR}/fonts" +exec ${MMC_DIR}/bin/MultiMC -d ${MMC_DIR} $@ -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./lib" -export QT_PLUGIN_PATH=$QT_PLUGIN_PATH:"." -exec ./bin/MultiMC diff --git a/translations/mmc_de.ts b/translations/mmc_de.ts new file mode 100644 index 00000000..42047378 --- /dev/null +++ b/translations/mmc_de.ts @@ -0,0 +1,1428 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="de"> +<context> + <name>AboutDialog</name> + <message> + <location filename="../gui/aboutdialog.ui" line="20"/> + <source>Dialog</source> + <translation>Dialog</translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="86"/> + <source>MultiMC</source> + <translation>MultiMC</translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="108"/> + <source>About</source> + <translation>Über</translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="114"/> + <source>MultiMC is a custom launcher that makes managing Minecraft easier by allowing you to have multiple installations of Minecraft at once.</source> + <translation>MultiMC ist ein alternativer Launcher, der das Management von Minecraft vereinfacht, indem er es dir erlaubt, mehrere Installationen von Minecraft zu verwalten.</translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="133"/> + <source>© 2013 MultiMC Contributors</source> + <translation>© 2013 MultiMC Contributors</translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="148"/> + <source><html><head/><body><p><a href="http://github.com/Forkk/MultiMC5"><span style=" text-decoration: underline; color:#0000ff;">http://github.com/MultiMC/MultiMC5</span></a></p></body></html></source> + <translation></translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="190"/> + <source>No Language file loaded.</source> + <extracomment>Hey, Translator, You are free to put a reference to you here :)</extracomment> + <translation>Deutsche Sprachdatei von Kilobyte (siehe oben).</translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="224"/> + <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2012 MultiMC Contributors</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">you may not use this file except in compliance with the License.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">You may obtain a copy of the License at</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> http://www.apache.org/licenses/LICENSE-2.0</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Unless required by applicable law or agreed to in writing, software</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">See the License for the specific language governing permissions and</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">limitations under the License.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">MultiMC uses QSLog, </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright (c) 2010, Razvan Petru</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">All rights reserved.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Redistribution and use in source and binary forms, with or without modification,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">are permitted provided that the following conditions are met:</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">* Redistributions of source code must retain the above copyright notice, this</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> list of conditions and the following disclaimer.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">* Redistributions in binary form must reproduce the above copyright notice, this</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> list of conditions and the following disclaimer in the documentation and/or other</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> materials provided with the distribution.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">* The name of the contributors may not be used to endorse or promote products</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> derived from this software without specific prior written permission.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot; AND</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">OF THE POSSIBILITY OF SUCH DAMAGE.</span></p></body></html></source> + <translation></translation> + </message> + <message> + <source><html><head/><body><p><a href="http://github.com/Forkk/MultiMC5"><span style=" text-decoration: underline; color:#0000ff;">http://github.com/Forkk/MultiMC5</span></a></p></body></html></source> + <translation type="obsolete"><html><head/><body><p><a href="http://github.com/Forkk/MultiMC5"><span style=" text-decoration: underline; color:#0000ff;">http://github.com/Forkk/MultiMC5</span></a></p></body></html></translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="167"/> + <source>Credits</source> + <translation>Dank an</translation> + </message> + <message> + <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Andrew Okin &lt;<a href="mailto:forkk@forkk.net"><span style=" text-decoration: underline; color:#0000ff;">forkk@forkk.net</span></a>&gt;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Petr Mrázek &lt;<a href="mailto:peterix@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">peterix@gmail.com</span></a>&gt;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Orochimarufan &lt;<a href="mailto:orochimarufan.x3@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">orochimarufan.x3@gmail.com</span></a>&gt;</p></body></html></source> + <translation type="obsolete"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Andrew Okin &lt;<a href="mailto:forkk@forkk.net"><span style=" text-decoration: underline; color:#0000ff;">forkk@forkk.net</span></a>&gt;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Petr Mrázek &lt;<a href="mailto:peterix@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">peterix@gmail.com</span></a>&gt;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Orochimarufan &lt;<a href="mailto:orochimarufan.x3@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">orochimarufan.x3@gmail.com</span></a>&gt;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Deutsche Übersetzung: Kilobyte &lt;<a href="mailto:stiepen22@gmx.de"><span style=" text-decoration: underline; color:#0000ff;">stiepen22@gmx.de</span></a>&gt;</p></body></html></translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="209"/> + <source>License</source> + <translation>Lizenz</translation> + </message> + <message> + <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2012 MultiMC Contributors</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">you may not use this file except in compliance with the License.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">You may obtain a copy of the License at</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> http://www.apache.org/licenses/LICENSE-2.0</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Unless required by applicable law or agreed to in writing, software</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">See the License for the specific language governing permissions and</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">limitations under the License.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">MultiMC uses bspatch, </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2003-2005 Colin Percival</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">All rights reserved</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Redistribution and use in source and binary forms, with or without</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">modification, are permitted providing that the following conditions</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">are met: </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">1. Redistributions of source code must retain the above copyright</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> notice, this list of conditions and the following disclaimer.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">2. Redistributions in binary form must reproduce the above copyright</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> notice, this list of conditions and the following disclaimer in the</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> documentation and/or other materials provided with the distribution.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">POSSIBILITY OF SUCH DAMAGE.</span></p></body></html></source> + <translation type="obsolete"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2012 MultiMC Contributors</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">you may not use this file except in compliance with the License.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">You may obtain a copy of the License at</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> http://www.apache.org/licenses/LICENSE-2.0</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Unless required by applicable law or agreed to in writing, software</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">See the License for the specific language governing permissions and</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">limitations under the License.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">MultiMC uses bspatch, </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2003-2005 Colin Percival</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">All rights reserved</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Redistribution and use in source and binary forms, with or without</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">modification, are permitted providing that the following conditions</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">are met: </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">1. Redistributions of source code must retain the above copyright</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> notice, this list of conditions and the following disclaimer.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">2. Redistributions in binary form must reproduce the above copyright</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> notice, this list of conditions and the following disclaimer in the</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;"> documentation and/or other materials provided with the distribution.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">POSSIBILITY OF SUCH DAMAGE.</span></p></body></html></translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="278"/> + <source>About Qt</source> + <translation>Über Qt</translation> + </message> + <message> + <location filename="../gui/aboutdialog.ui" line="298"/> + <source>Close</source> + <translation>Schließen</translation> + </message> +</context> +<context> + <name>ConsoleWindow</name> + <message> + <location filename="../gui/consolewindow.ui" line="14"/> + <source>MultiMC Console</source> + <translation>MultiMC-Konsole</translation> + </message> + <message> + <location filename="../gui/consolewindow.ui" line="68"/> + <source>Kill Minecraft</source> + <translation>Minecraft Killen</translation> + </message> + <message> + <location filename="../gui/consolewindow.ui" line="75"/> + <source>Close</source> + <translation>Schließen</translation> + </message> + <message> + <location filename="../gui/consolewindow.cpp" line="93"/> + <source>Kill Minecraft?</source> + <extracomment>Main question of the kill confirmation dialog</extracomment> + <translation>Minecraft Killen?</translation> + </message> + <message> + <location filename="../gui/consolewindow.cpp" line="94"/> + <source>This can cause the instance to get corrupted and should only be used if Minecraft is frozen for some reason</source> + <translation>Dies kann diese Instanz beschädigen und sollte daher nur genutzt werden, wenn Minecraft eingefroren ist</translation> + </message> +</context> +<context> + <name>EditNotesDialog</name> + <message> + <location filename="../gui/EditNotesDialog.ui" line="14"/> + <source>Edit Notes</source> + <translation>Notizen bearbeiten</translation> + </message> + <message> + <location filename="../gui/EditNotesDialog.cpp" line="15"/> + <source>Edit notes of %1</source> + <translation>Notizen von %1 bearbeiten</translation> + </message> +</context> +<context> + <name>IconPickerDialog</name> + <message> + <location filename="../gui/IconPickerDialog.ui" line="14"/> + <source>Pick icon</source> + <translation>Symbol auswählen</translation> + </message> + <message> + <location filename="../gui/IconPickerDialog.cpp" line="44"/> + <source>Add Icon</source> + <translation>Symbol hinzufügen</translation> + </message> + <message> + <location filename="../gui/IconPickerDialog.cpp" line="45"/> + <source>Remove Icon</source> + <translation>Symbol entfernen</translation> + </message> + <message> + <location filename="../gui/IconPickerDialog.cpp" line="91"/> + <source>Select Icons</source> + <extracomment>The title of the select icons open file dialog</extracomment> + <translation>Symbol auswählen</translation> + </message> + <message> + <location filename="../gui/IconPickerDialog.cpp" line="93"/> + <source>Icons</source> + <extracomment>The type of icon files</extracomment> + <translation>Symbole</translation> + </message> +</context> +<context> + <name>InstanceSettings</name> + <message> + <location filename="../gui/instancesettings.ui" line="27"/> + <source>Minecraft</source> + <translation></translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="36"/> + <source>Window Size</source> + <translation>Fenstergröße</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="48"/> + <source>Start Minecraft maximized?</source> + <translation>Minecraft maximiert starten?</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="57"/> + <source>Window height:</source> + <translation>Fensterhöhe:</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="64"/> + <source>Window width:</source> + <translation>Fensterbreite:</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="108"/> + <source>Console Settings</source> + <translation>Konsoleneinstellungen</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="120"/> + <source>Show console while the game is running?</source> + <translation>Konsole anzeigen wenn das Spiel läuft?</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="127"/> + <source>Automatically close console when the game quits?</source> + <translation>Konsole automatisch schließen, nachdem das Spiel beendet wurde?</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="140"/> + <source>Account Settings</source> + <translation>Konteneinstellungen</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="155"/> + <source>Login automatically when an instance icon is double clicked?</source> + <translation>Automatisch einloggen, wenn das Instanzsymbol doppelt gecklickt wurde?</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="182"/> + <source>Java</source> + <translation>Java</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="191"/> + <source>Memory</source> + <translation>Arbeitsspeicher</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="219"/> + <source>Minimum memory allocation:</source> + <translation>Min. Arbeitspeicher:</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="226"/> + <source>Maximum memory allocation:</source> + <translation>Max. Arbeitspeicher:</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="265"/> + <source>PermGen:</source> + <translation>PermGen:</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="278"/> + <source>Java Settings</source> + <translation>Java-Einstellungen</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="290"/> + <source>Java path:</source> + <translation>Java-Pfad:</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="300"/> + <source>JVM arguments:</source> + <translation>JVM-Argumente:</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="307"/> + <source>Auto-detect</source> + <translation>Automatisch erkennen</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="323"/> + <source>Custom Commands</source> + <translation>Eigene Befehle</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="338"/> + <source>Post-exit command:</source> + <translation>Nach-abschluss-Befehl:</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="345"/> + <source>Pre-launch command:</source> + <translation>Vor-Start-Befehl:</translation> + </message> + <message> + <location filename="../gui/instancesettings.ui" line="367"/> + <source>Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working directory with INST_ID, INST_DIR, and INST_NAME as environment variables.</source> + <translation>Vor-Start wird ausgeführt, bevor die Instanz startet, Nach-Ende nachdem die Instanz beendet wurde. Beide werden Im ausführungsverzeichnis von MultiMC gestartet. Verfügbare Umgebungsvariablen: INST_ID, INST_DIR, INST_NAME.</translation> + </message> +</context> +<context> + <name>LWJGLSelectDialog</name> + <message> + <location filename="../gui/lwjglselectdialog.ui" line="14"/> + <source>Dialog</source> + <translation></translation> + </message> + <message> + <location filename="../gui/lwjglselectdialog.ui" line="20"/> + <source>Status label...</source> + <translation></translation> + </message> + <message> + <location filename="../gui/lwjglselectdialog.ui" line="32"/> + <source>&Refresh</source> + <translation>Anktualisie&ren</translation> + </message> + <message> + <location filename="../gui/lwjglselectdialog.cpp" line="59"/> + <source>Loading LWJGL version list...</source> + <translation>Lade LWJGL-Versionsliste...</translation> + </message> +</context> +<context> + <name>LegacyModEditDialog</name> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="14"/> + <source>Dialog</source> + <translation></translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="24"/> + <source>Jar Mods</source> + <translation>Jar-Mods</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="42"/> + <location filename="../gui/LegacyModEditDialog.ui" line="108"/> + <location filename="../gui/LegacyModEditDialog.ui" line="163"/> + <location filename="../gui/LegacyModEditDialog.ui" line="221"/> + <source>&Add</source> + <translation>&Hinzufügen</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="49"/> + <location filename="../gui/LegacyModEditDialog.ui" line="115"/> + <location filename="../gui/LegacyModEditDialog.ui" line="170"/> + <location filename="../gui/LegacyModEditDialog.ui" line="228"/> + <source>&Remove</source> + <translation>&Entfernen</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="56"/> + <source>MCForge</source> + <translation></translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="76"/> + <source>Move &Up</source> + <translation>&Nach Oben</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="83"/> + <source>Move &Down</source> + <translation>Nach &Unten</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="93"/> + <source>Core Mods</source> + <translation>Coremods</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="135"/> + <location filename="../gui/LegacyModEditDialog.ui" line="190"/> + <location filename="../gui/LegacyModEditDialog.ui" line="248"/> + <source>&View Folder</source> + <translation>&Ordner öffnen</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="145"/> + <source>Loader Mods</source> + <translation>Normale Mods</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.ui" line="203"/> + <source>Texture Packs</source> + <translation>Texturenpacks</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.cpp" line="249"/> + <location filename="../gui/LegacyModEditDialog.cpp" line="246"/> + <source>Select Loader Mods</source> + <extracomment>Title of regular mod selection dialog</extracomment> + <translation>Mods auswählen</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.cpp" line="276"/> + <source>Select Resource Packs</source> + <translation>Resourcenpacks auswählen</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.cpp" line="188"/> + <source>Select Core Mods</source> + <extracomment>Title of core mod selection dialog</extracomment> + <translation>Coremodsauswählen</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.cpp" line="235"/> + <source>Select Jar Mods</source> + <extracomment>Title of jar mod selection dialog</extracomment> + <translation>Jarmods auswählen</translation> + </message> + <message> + <location filename="../gui/LegacyModEditDialog.cpp" line="257"/> + <source>Select Texture Packs</source> + <extracomment>Title of texture pack selection dialog</extracomment> + <translation>Texturenpacks auswählen</translation> + </message> +</context> +<context> + <name>LoginDialog</name> + <message> + <location filename="../gui/logindialog.ui" line="14"/> + <source>Login</source> + <translation>Einloggen</translation> + </message> + <message> + <location filename="../gui/logindialog.ui" line="20"/> + <source><span style=" color:#ff0000;">Error</span></source> + <translation></translation> + </message> + <message> + <location filename="../gui/logindialog.ui" line="29"/> + <source>Username:</source> + <translation>Nutzername:</translation> + </message> + <message> + <location filename="../gui/logindialog.ui" line="43"/> + <source>Password:</source> + <translation>Passwort:</translation> + </message> + <message> + <location filename="../gui/logindialog.ui" line="53"/> + <source>Password</source> + <translation>Passwort</translation> + </message> + <message> + <location filename="../gui/logindialog.ui" line="66"/> + <source>Forget</source> + <translation>Vergessen</translation> + </message> + <message> + <location filename="../gui/logindialog.ui" line="83"/> + <source>&Remember Username?</source> + <translation>&Nutzernamen speichern?</translation> + </message> + <message> + <location filename="../gui/logindialog.ui" line="96"/> + <source>R&emember Password?</source> + <translation>&Passwort speichern?</translation> + </message> + <message> + <location filename="../gui/logindialog.cpp" line="28"/> + <source>Offline Once</source> + <extracomment>Use offline mode one time</extracomment> + <translation>Einmal Offline</translation> + </message> + <message> + <location filename="../gui/logindialog.cpp" line="37"/> + <source>Name</source> + <extracomment>The username during login (placeholder)</extracomment> + <translation>Name</translation> + </message> +</context> +<context> + <name>LoginTask</name> + <message> + <location filename="../logic/net/LoginTask.cpp" line="41"/> + <location filename="../logic/net/LoginTask.cpp" line="135"/> + <source>Logging in...</source> + <translation>Einloggen...</translation> + </message> + <message> + <source>Failed to parse Minecraft version string.</source> + <translation type="obsolete">Konnte Minecraft-Versionsstring nicht parsen.</translation> + </message> + <message> + <location filename="../logic/net/LoginTask.cpp" line="124"/> + <source>Invalid username or password.</source> + <translation>Falsche Kombination von Nutzernamen und Passwort.</translation> + </message> + <message> + <location filename="../logic/net/LoginTask.cpp" line="126"/> + <source>Launcher outdated, please update.</source> + <translation>Veralteter Launcher, Bitte lade ein Update herunter.</translation> + </message> + <message> + <location filename="../logic/net/LoginTask.cpp" line="93"/> + <location filename="../logic/net/LoginTask.cpp" line="128"/> + <location filename="../logic/net/LoginTask.cpp" line="205"/> + <location filename="../logic/net/LoginTask.cpp" line="232"/> + <source>Login failed: %1</source> + <translation>Login fehlgeschlagen: %1</translation> + </message> + <message> + <location filename="../logic/net/LoginTask.cpp" line="77"/> + <location filename="../logic/net/LoginTask.cpp" line="189"/> + <source>The login servers are currently unavailable. Check http://help.mojang.com/ for more info.</source> + <translation>Derzeit kann auf die Login-Server nicht zugegriffe werden. Für weitere Informationen siehe http://help.mojang.com/.</translation> + </message> + <message> + <location filename="../logic/net/LoginTask.cpp" line="82"/> + <location filename="../logic/net/LoginTask.cpp" line="194"/> + <source>Login failed: Unknown HTTP error %1 occurred.</source> + <translation>Login fehlgeschlagen. Unbekannter HTTP-Fehler: %1.</translation> + </message> + <message> + <location filename="../logic/net/LoginTask.cpp" line="89"/> + <location filename="../logic/net/LoginTask.cpp" line="201"/> + <source>Login canceled.</source> + <translation>Login abgebrochen.</translation> + </message> + <message> + <location filename="../logic/net/LoginTask.cpp" line="238"/> + <source>Login failed: BAD FORMAT #1</source> + <translatorcomment>Login fehlgeschlagen: UNGÜLTIGES FORMAT #1</translatorcomment> + <translation></translation> + </message> +</context> +<context> + <name>MainWindow</name> + <message> + <location filename="../gui/mainwindow.ui" line="14"/> + <source>MultiMC 5</source> + <translation>MultiMC 5</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="40"/> + <source>Main Toolbar</source> + <translation>Haupt-Werkzeugleiste</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="80"/> + <source>Instance Toolbar</source> + <translation>Instanz-Werkzeugleiste</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="123"/> + <source>Add Instance</source> + <translation>Instanz hinzufügen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="126"/> + <location filename="../gui/mainwindow.ui" line="129"/> + <source>Add a new instance.</source> + <translation>Neue Instanz erstellen.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="138"/> + <source>View Instance Folder</source> + <translation>Instanzordner öffnen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="141"/> + <location filename="../gui/mainwindow.ui" line="144"/> + <source>Open the instance folder in a file browser.</source> + <translation>Instanzordner im Dateimanager öffnen.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="153"/> + <source>Refresh</source> + <translation>Aktualisieren</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="156"/> + <location filename="../gui/mainwindow.ui" line="159"/> + <source>Reload the instance list.</source> + <translation>Instanzliste neu Laden.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="168"/> + <source>View Central Mods Folder</source> + <translation>Zenstralen Modordner öffnen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="171"/> + <location filename="../gui/mainwindow.ui" line="174"/> + <source>Open the central mods folder in a file browser.</source> + <translation>Zentralen Modordner in einem Dateimanager öffnen.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="183"/> + <source>Check for Updates</source> + <translation>Auf Updates überprüfen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="186"/> + <location filename="../gui/mainwindow.ui" line="189"/> + <source>Check for new updates for MultiMC</source> + <translation>Auf Updates für MultiMC prüfen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="198"/> + <location filename="../gui/mainwindow.ui" line="325"/> + <source>Settings</source> + <translation>Einstellungen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="201"/> + <location filename="../gui/mainwindow.ui" line="204"/> + <source>Change settings.</source> + <translation>Einstellungen ändern.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="216"/> + <source>Report a Bug</source> + <translation>Fehler melden</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="219"/> + <location filename="../gui/mainwindow.ui" line="222"/> + <source>Open the bug tracker to report a bug with MultiMC.</source> + <translation>Fehler-Verfolgung öffnen, um einen Fehler zu melden (Bitte auf Englisch ;))</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="231"/> + <source>News</source> + <translation>Neuigkeiten</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="234"/> + <location filename="../gui/mainwindow.ui" line="237"/> + <source>Open the MultiMC dev blog to read news about MultiMC.</source> + <translation>Den MultiMC Entwicklerblog öffnen und Neuigkeiten über MultiMC erfahren.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="246"/> + <location filename="../gui/mainwindow.ui" line="252"/> + <source>About MultiMC</source> + <translation>Über MultiMC</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="249"/> + <source>View information about MultiMC.</source> + <translation>Informationen über MultiMC anzeigen.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="260"/> + <source>Play</source> + <translation>Spielen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="263"/> + <location filename="../gui/mainwindow.ui" line="266"/> + <source>Launch the selected instance.</source> + <translation>Die ausgewählte Instanz starten.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="271"/> + <source>Instance Name</source> + <translation>Instanzname</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="274"/> + <location filename="../gui/mainwindow.ui" line="277"/> + <source>Rename the selected instance.</source> + <translation>Ausgewählte Instanz umbenennen.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="282"/> + <source>Change Group</source> + <translation>Gruppe ändern</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="285"/> + <location filename="../gui/mainwindow.ui" line="288"/> + <source>Change the selected instance's group.</source> + <translation>Die Gruppe der ausgewählten Instanz ändern.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="300"/> + <source>Change Icon</source> + <translation>Symbol ändern</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="303"/> + <location filename="../gui/mainwindow.ui" line="306"/> + <source>Change the selected instance's icon.</source> + <translation>Das Symbol der ausgewählten Instanz ändern.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="311"/> + <source>Edit Notes</source> + <translation>Notizen bearbeiten</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="314"/> + <location filename="../gui/mainwindow.ui" line="317"/> + <source>Edit the notes for the selected instance.</source> + <translation>Notizen für die ausgewählte Instanz bearbeiten.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="328"/> + <location filename="../gui/mainwindow.ui" line="331"/> + <source>Change settings for the selected instance.</source> + <translation>Einstellungen für die ausgewählte Instanz bearbeiten.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="339"/> + <source>Make Shortcut</source> + <translation>Verknüpfung erstellen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="342"/> + <location filename="../gui/mainwindow.ui" line="345"/> + <source>Make a shortcut on the desktop for the selected instance.</source> + <translation>Erstellt eine Verknüpfung für die ausgewählte Instanz auf dem Desktop.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="353"/> + <source>Manage Saves</source> + <translation>Speicherstände verwalten</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="356"/> + <location filename="../gui/mainwindow.ui" line="359"/> + <source>Manage saves for the selected instance.</source> + <translation>Die Speicherstände der ausgewählten Instanz verwalten.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="364"/> + <source>Edit Mods</source> + <translation>Mods bearbeiten</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="367"/> + <location filename="../gui/mainwindow.ui" line="370"/> + <source>Edit the mods for the selected instance.</source> + <translation>Die Mods der ausgewähten Instanz bearbeiten.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="378"/> + <source>Change Version</source> + <translation>Version ändern</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="381"/> + <location filename="../gui/mainwindow.ui" line="384"/> + <source>Change the selected instance's Minecraft version.</source> + <translation>Die Minecraftversion der ausgewählten Instanz ändern.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="392"/> + <source>Change LWJGL</source> + <translation>LWJGL ändern</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="395"/> + <location filename="../gui/mainwindow.ui" line="398"/> + <source>Change the version of LWJGL for the selected instance to use.</source> + <translation>Die zu benutzende Version von LWJGL für die aktuelle Instanz ändern.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="403"/> + <source>Instance Folder</source> + <translation>Instanzordner</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="406"/> + <location filename="../gui/mainwindow.ui" line="409"/> + <source>Open the selected instance's root folder in a file browser.</source> + <translation>Den Wurzelordner der Aktuellen Instanz im Dateimanager öffnen.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="414"/> + <source>Delete</source> + <translation>Löschen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="417"/> + <location filename="../gui/mainwindow.ui" line="420"/> + <source>Delete the selected instance.</source> + <translation>Ausgewählte Instanz löschen.</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="425"/> + <source>Config Folder</source> + <translation>Konfig-ordner</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="428"/> + <source>Open the instance's config folder</source> + <translation>Den Konfigurationsordner im Dateimanager anzeigen</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="440"/> + <source>Meow</source> + <translation>Miau</translation> + </message> + <message> + <location filename="../gui/mainwindow.ui" line="443"/> + <source><html><head/><body><p align="center"><span style=" font-weight:600; color:#ff0004;">Catnarok!</span></p><p align="center">Or just a cat with a ball of yarn?</p><p align="center"><span style=" font-style:italic;">WHO KNOWS?!</span></p><p align="center"><img src=":/icons/instances/tnt"/></p></body></html></source> + <translation></translation> + </message> + <message> + <source><html><head/><body><p align="center"><span style=" font-weight:600; color:#ff0004;">Catnatok!</span></p><p align="center">Or just a cat with a ball of yarn?</p><p align="center"><span style=" font-style:italic;">WHO KNOWS?!</span></p><p align="center"><img src=":/icons/instances/tnt"/></p></body></html></source> + <translation type="obsolete"><html><head/><body><p align="center"><span style=" font-weight:600; color:#ff0004;">Catnatok!</span></p><p align="center">Or just a cat with a ball of yarn?</p><p align="center"><span style=" font-style:italic;">WHO KNOWS?!</span></p><p align="center"><img src=":/icons/instances/tnt"/></p></body></html></translation> + </message> + <message> + <location filename="../gui/mainwindow.cpp" line="313"/> + <source>Group name</source> + <translation>Gruppenname</translation> + </message> + <message> + <location filename="../gui/mainwindow.cpp" line="313"/> + <source>Enter a new group name.</source> + <translation>Neuen Gruppennamen eingeben.</translation> + </message> + <message> + <location filename="../gui/mainwindow.cpp" line="398"/> + <source>Instance name</source> + <translation>Instanzname</translation> + </message> + <message> + <location filename="../gui/mainwindow.cpp" line="398"/> + <source>Enter a new instance name.</source> + <translation>Neuen Instanznamen eingeben.</translation> + </message> + <message> + <location filename="../gui/mainwindow.cpp" line="580"/> + <source>MultiMC Shortcut</source> + <translation>MultiMC-Verknüpfung</translation> + </message> + <message> + <location filename="../gui/mainwindow.cpp" line="580"/> + <source>Enter a Shortcut Name.</source> + <translation>Verknüpfungsnamen eingeben.</translation> + </message> + <message> + <location filename="../gui/mainwindow.cpp" line="588"/> + <source>Not useful</source> + <translation>Sinnlos</translation> + </message> + <message> + <location filename="../gui/mainwindow.cpp" line="589"/> + <source>A Dummy Shortcut was created. it will not do anything productive</source> + <translation>Eine Dummy-Verknüpfung wurde erstellt. Sie wird jedoch absolut nichts bewirken</translation> + </message> + <message> + <location filename="../gui/mainwindow.cpp" line="665"/> + <source>Rename Instance</source> + <translation>Instanz umbenennen</translation> + </message> +</context> +<context> + <name>MinecraftProcess</name> + <message> + <location filename="../logic/MinecraftProcess.cpp" line="123"/> + <source>Minecraft exited with exitcode %1.</source> + <extracomment>Message displayed on instance exit</extracomment> + <translation>Minecraft wurde mit exitcode %1 beendet.</translation> + </message> + <message> + <location filename="../logic/MinecraftProcess.cpp" line="126"/> + <source>Minecraft was killed by user.</source> + <extracomment>Message displayed after the instance exits due to kill request</extracomment> + <translation>Minecraft wurde durch den nutzer gekillt.</translation> + </message> + <message> + <location filename="../logic/MinecraftProcess.cpp" line="175"/> + <source>Could not launch minecraft!</source> + <extracomment>Error message displayed if instace can't start</extracomment> + <translation>Konnte Minecraft nicht starten!</translation> + </message> +</context> +<context> + <name>MultiMC</name> + <message> + <source>display this help and exit.</source> + <translation type="obsolete">Zeigt diese Hilfe und beended das Programm.</translation> + </message> + <message> + <source>display program version and exit.</source> + <translation type="obsolete">Zeigt die programmversion an und beendet das Programm.</translation> + </message> + <message> + <source>use the supplied directory as MultiMC root instead of the binary location (use '.' for current)</source> + <translation type="obsolete">Benutze das angegebene Verzeichnis als Arbeitsverzeichnis anstelle des speicherorts. (Benutze '.' um das aktuele Verzeichnis zu verwenden)</translation> + </message> + <message> + <source>replaces the given file with the running executable</source> + <translation type="obsolete">Ersetzt die angegebene Datei mit der laufenden Anwendung</translation> + </message> + <message> + <source><path></source> + <translation type="obsolete"><pfad></translation> + </message> + <message> + <source>doesn't restart MultiMC after installing updates</source> + <translation type="obsolete">MultiMC nach dem Update nicht neu starten</translation> + </message> + <message> + <source>tries to launch the given instance</source> + <translation type="obsolete">Versucht die angegebene Instanz zu starten</translation> + </message> + <message> + <source><inst></source> + <translation type="obsolete"><instanz></translation> + </message> + <message> + <source>CommandLineError: </source> + <translation type="obsolete">KommandoZeilenFehler:</translation> + </message> + <message> + <source>Try '%1 -h' to get help on MultiMC's command line parameters.</source> + <translation type="obsolete">Versuche '%1 -h' um Hilfe zu MultiMCs Kommandozeilenparametern zu bekommen.</translation> + </message> + <message> + <source>Performing MultiMC update: </source> + <translation type="obsolete">Führe MultiMC-Update durch: </translation> + </message> + <message> + <source>Loading Instances...</source> + <translation type="obsolete">Lade Instanzen...</translation> + </message> +</context> +<context> + <name>NewInstanceDialog</name> + <message> + <location filename="../gui/newinstancedialog.ui" line="17"/> + <source>New Instance</source> + <translation>Neue Instanz</translation> + </message> + <message> + <location filename="../gui/newinstancedialog.ui" line="74"/> + <source>Name</source> + <translation>Name</translation> + </message> + <message> + <location filename="../gui/newinstancedialog.ui" line="90"/> + <source>Version:</source> + <translation>Version:</translation> + </message> + <message> + <location filename="../gui/newinstancedialog.ui" line="104"/> + <source>...</source> + <translation>...</translation> + </message> +</context> +<context> + <name>OneSixModEditDialog</name> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="14"/> + <source>Dialog</source> + <translatorcomment>Am i really responsible for this?</translatorcomment> + <translation>Edit Mods</translation> + </message> + <message> + <source>Library</source> + <translation type="obsolete">Bibliothek</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="158"/> + <source>Loader Mods</source> + <translation>Mods</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="122"/> + <location filename="../gui/OneSixModEditDialog.ui" line="176"/> + <location filename="../gui/OneSixModEditDialog.ui" line="231"/> + <source>&Add</source> + <translation>&Hinzufügen</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="33"/> + <source>Version</source> + <translation>Version</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="53"/> + <source>Main Class:</source> + <translation>Hauptklasse:</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="73"/> + <source>Replace any current custom version with Minecraft Forge</source> + <translation>Die aktuelle benutzerdefinierte Version mit Minecraft Forge ersetzen</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="76"/> + <source>Install Forge</source> + <translation>Forge Installieren</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="83"/> + <source>Create an customized copy of the base version</source> + <translation>Eine modifizierbare Kopie der Version erstellen</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="86"/> + <source>Customize</source> + <translation>Benutzerdefiniert</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="96"/> + <source>Revert to original base version</source> + <translation>Benutzerdefinierte Einstellungen zurücksetzen</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="99"/> + <source>Revert</source> + <translation>Zurücksetzen</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="119"/> + <source>Add new libraries</source> + <translation></translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="132"/> + <source>Remove selected libraries</source> + <translation></translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="135"/> + <location filename="../gui/OneSixModEditDialog.ui" line="183"/> + <location filename="../gui/OneSixModEditDialog.ui" line="238"/> + <source>&Remove</source> + <translation>&Entfernen</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="203"/> + <location filename="../gui/OneSixModEditDialog.ui" line="258"/> + <source>&View Folder</source> + <translation>&Ordner öffnen</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.ui" line="213"/> + <source>Resource Packs</source> + <translation>Resourcenpacks</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.cpp" line="108"/> + <location filename="../gui/OneSixModEditDialog.cpp" line="131"/> + <source>Revert?</source> + <translation>Zurücksetzen?</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.cpp" line="108"/> + <source>Do you want to revert the version of this instance to its original configuration?</source> + <translation>Möchtest du wirklich die Version dieser Instanz zurücksetzen?</translation> + </message> + <message> + <location filename="../gui/OneSixModEditDialog.cpp" line="132"/> + <source>This will revert any changes you did to the version up to this point. Is that OK?</source> + <translation>Dies wird alle Änderungen, die du vorgenommen hast zurücksetzen. Bist du damit einverstanden?</translation> + </message> +</context> +<context> + <name>ProgressDialog</name> + <message> + <location filename="../gui/ProgressDialog.ui" line="26"/> + <source>Please wait...</source> + <translation>Bitte warten...</translation> + </message> + <message> + <location filename="../gui/ProgressDialog.ui" line="32"/> + <source>Task Status...</source> + <translation>Aufgabenstatus...</translation> + </message> +</context> +<context> + <name>SettingsDialog</name> + <message> + <location filename="../gui/settingsdialog.ui" line="20"/> + <source>Settings</source> + <translation>Einstellungen</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="40"/> + <source>General</source> + <translation>Generell</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="49"/> + <source>Sorting Mode</source> + <translation>Sortiermodus</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="55"/> + <source>By last launched</source> + <translation>Nach letzem Start</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="65"/> + <source>By name</source> + <translation>Nach Namen</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="78"/> + <source>Update Settings</source> + <translation>Updateeinstellungen</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="84"/> + <source>Use development builds?</source> + <translation>Entwicklerversionen benutzen?</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="91"/> + <source>Check for updates when MultiMC starts?</source> + <translation>Beim Start nach Updates suchen?</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="101"/> + <source>Folders</source> + <translation>Ordner</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="107"/> + <source>Instances:</source> + <translation>Instanzen:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="117"/> + <location filename="../gui/settingsdialog.ui" line="134"/> + <location filename="../gui/settingsdialog.ui" line="151"/> + <source>...</source> + <translation>...</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="124"/> + <source>Mods:</source> + <translation>Mods:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="141"/> + <source>LWJGL:</source> + <translation>LWJGL:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="175"/> + <source>Minecraft</source> + <translation>Minecraft</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="181"/> + <source>Window Size</source> + <translation>Fenstergröße</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="187"/> + <source>Start Minecraft maximized?</source> + <translation>Minecraft maximiert starten?</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="196"/> + <source>Window height:</source> + <translation>Fensterhöhe:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="203"/> + <source>Window width:</source> + <translation>Fensterbreite:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="244"/> + <source>Console Settings</source> + <translation>Konsoleneinstellungen</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="250"/> + <source>Show console while the game is running?</source> + <translation>Konsole anzeigen wenn das Spiel läuft?</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="257"/> + <source>Automatically close console when the game quits?</source> + <translation>Konsole automatisch schließen, nachdem das Spiel beendet wurde?</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="267"/> + <source>Login automatically when an instance icon is double clicked?</source> + <translation>Automatisch einloggen, wenn das Instanzsymbol doppelt gecklickt wurde?</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="288"/> + <source>Java</source> + <translation>Java</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="294"/> + <source>Memory</source> + <translation>Arbeitsspeicher</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="316"/> + <source>Minimum memory allocation:</source> + <translation>Min. Arbeitspeicher:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="323"/> + <source>Maximum memory allocation:</source> + <translation>Max. Arbeitspeicher:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="346"/> + <source>PermGen:</source> + <translation>PermGen:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="372"/> + <source>Java Settings</source> + <translation>Java-Einstellungen</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="378"/> + <source>Java path:</source> + <translation>Java-Pfad:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="385"/> + <source>JVM arguments:</source> + <translation>JVM-Argumente:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="398"/> + <source>Browse...</source> + <translation>Durchsuchen...</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="417"/> + <source>Auto-detect</source> + <translation>Automatisch erkennen</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="427"/> + <source>Custom Commands</source> + <translation>Eigene Befehle</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="433"/> + <source>Post-exit command:</source> + <translation>Nach-abschluss-Befehl:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="440"/> + <source>Pre-launch command:</source> + <translation>Vor-Start-Befehl:</translation> + </message> + <message> + <location filename="../gui/settingsdialog.ui" line="462"/> + <source>Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working directory with INST_ID, INST_DIR, and INST_NAME as environment variables.</source> + <translation>Vor-Start wird ausgeführt, bevor die Instanz startet, Nach-Ende nachdem die Instanz beendet wurde. Beide werden Im ausführungsverzeichnis von MultiMC gestartet. Verfügbare Umgebungsvariablen: INST_ID, INST_DIR, INST_NAME.</translation> + </message> + <message> + <location filename="../gui/settingsdialog.cpp" line="53"/> + <source>Instance Directory</source> + <translation>Instanz-Ordner</translation> + </message> + <message> + <location filename="../gui/settingsdialog.cpp" line="61"/> + <source>Mods Directory</source> + <translation>Modordner</translation> + </message> + <message> + <location filename="../gui/settingsdialog.cpp" line="69"/> + <source>LWJGL Directory</source> + <translation>LWJGL-Ordner</translation> + </message> + <message> + <location filename="../gui/settingsdialog.cpp" line="103"/> + <source>Development builds</source> + <translation>Entwicklerversionen</translation> + </message> + <message> + <location filename="../gui/settingsdialog.cpp" line="104"/> + <source>Development builds contain experimental features and may be unstable. Are you sure you want to enable them?</source> + <translation>Entwicklerversionen enthalten experimentelle Features und können instabil sein. Möchtest du sie dennoch aktivieren?</translation> + </message> + <message> + <location filename="../gui/settingsdialog.cpp" line="195"/> + <source>Find Java executable</source> + <translatorcomment>Umm... this translation is a bit meh</translatorcomment> + <translation type="unfinished">Java-Anwendung finden</translation> + </message> +</context> +<context> + <name>TaskDialog</name> + <message> + <source>Please wait...</source> + <translation type="obsolete">Bitte warten...</translation> + </message> + <message> + <source>Task Status...</source> + <translation type="obsolete">Aufgabenstatus...</translation> + </message> +</context> +<context> + <name>VersionSelectDialog</name> + <message> + <location filename="../gui/versionselectdialog.ui" line="14"/> + <source>Dialog</source> + <translation>Dialog</translation> + </message> + <message> + <source>Show &snapshots?</source> + <translation type="obsolete">'&Snapshots' anzeigen?</translation> + </message> + <message> + <source>Show &Nostalgia?</source> + <translation type="obsolete">'&Nostalgia'-Versionen anzeigen?</translation> + </message> + <message> + <location filename="../gui/versionselectdialog.ui" line="47"/> + <source>Reloads the version list.</source> + <translation>Instanzliste aktualisieren.</translation> + </message> + <message> + <location filename="../gui/versionselectdialog.ui" line="50"/> + <source>&Refresh</source> + <translation>&Aktualisieren</translation> + </message> +</context> +</TS> |