From a40c5fdd40920bd0de25716612e5915cf25bdf67 Mon Sep 17 00:00:00 2001 From: huangyuhui Date: Mon, 1 Jan 2018 23:12:59 +0800 Subject: [PATCH] Turn to java --- .../main/kotlin/org/jackhuang/hmcl/Main.kt | 23 +- .../org/jackhuang/hmcl/game/AccountHelper.kt | 11 +- .../jackhuang/hmcl/game/HMCLGameRepository.kt | 6 +- .../org/jackhuang/hmcl/game/HMCLModpack.kt | 48 +- .../org/jackhuang/hmcl/game/LauncherHelper.kt | 27 +- .../org/jackhuang/hmcl/game/ModpackHelper.kt | 35 +- .../org/jackhuang/hmcl/setting/Profile.kt | 4 +- .../org/jackhuang/hmcl/setting/Settings.kt | 27 +- .../hmcl/setting/SettingsConstants.kt | 11 +- .../jackhuang/hmcl/setting/VersionSetting.kt | 56 +- .../org/jackhuang/hmcl/ui/AccountItem.kt | 5 +- .../org/jackhuang/hmcl/ui/AccountsPage.kt | 12 +- .../org/jackhuang/hmcl/ui/Controllers.kt | 2 +- .../kotlin/org/jackhuang/hmcl/ui/FXUtils.kt | 5 +- .../jackhuang/hmcl/ui/InstallerController.kt | 12 +- .../jackhuang/hmcl/ui/LeftPaneController.kt | 8 +- .../kotlin/org/jackhuang/hmcl/ui/LogWindow.kt | 5 + .../kotlin/org/jackhuang/hmcl/ui/MainPage.kt | 12 +- .../org/jackhuang/hmcl/ui/ModController.kt | 9 +- .../kotlin/org/jackhuang/hmcl/ui/ModItem.kt | 4 +- .../hmcl/ui/VersionSettingsController.kt | 20 +- .../hmcl/ui/YggdrasilAccountLoginPane.kt | 9 +- .../ui/download/DownloadWizardProvider.kt | 9 +- .../hmcl/ui/download/InstallWizardProvider.kt | 20 +- .../hmcl/ui/download/VersionsPage.kt | 5 +- .../hmcl/ui/export/ExportWizardProvider.kt | 10 +- .../hmcl/ui/wizard/AbstractWizardDisplayer.kt | 19 +- .../jackhuang/hmcl/upgrade/AppDataUpgrader.kt | 31 +- .../jackhuang/hmcl/upgrade/UpdateChecker.kt | 10 +- .../kotlin/org/jackhuang/hmcl/util/Lang.kt | 107 +- .../kotlin/org/jackhuang/hmcl/util/Lib.kt | 0 .../org/jackhuang/hmcl/util/Properties.kt | 0 .../jackhuang/hmcl/util/ReflectionHelper.kt | 0 .../java/org/jackhuang/hmcl/auth/Account.java | 40 + .../jackhuang/hmcl/auth/AccountFactory.java} | 22 +- .../org/jackhuang/hmcl/auth/AuthInfo.java | 96 + .../hmcl/auth/AuthenticationException.java | 37 + .../jackhuang/hmcl/auth/OfflineAccount.java | 81 + .../hmcl/auth/OfflineAccountFactory.java | 55 + .../org/jackhuang/hmcl/auth/UserType.java | 49 + .../auth/yggdrasil/AuthenticationRequest.java | 61 + .../hmcl/auth/yggdrasil/GameProfile.java | 102 + .../InvalidCredentialsException.java | 37 + .../yggdrasil/InvalidTokenException.java} | 28 +- .../hmcl/auth/yggdrasil/Property.java} | 25 +- .../hmcl/auth/yggdrasil/PropertyMap.java | 116 ++ .../hmcl/auth/yggdrasil/RefreshRequest.java | 62 + .../hmcl/auth/yggdrasil/Response.java | 82 + .../jackhuang/hmcl/auth/yggdrasil/User.java | 56 + .../hmcl/auth/yggdrasil/ValidateRequest.java} | 33 +- .../hmcl/auth/yggdrasil/YggdrasilAccount.java | 274 +++ .../yggdrasil/YggdrasilAccountFactory.java | 70 + .../download/AbstractDependencyManager.java} | 20 +- .../download/BMCLAPIDownloadProvider.java | 88 + .../download/DefaultDependencyManager.java | 100 + .../hmcl/download/DefaultGameBuilder.java | 74 + .../hmcl/download/DependencyManager.java} | 29 +- .../hmcl/download/DownloadProvider.java} | 28 +- .../jackhuang/hmcl/download/GameBuilder.java} | 46 +- .../hmcl/download/MojangDownloadProvider.java | 84 + .../hmcl/download/RemoteVersion.java | 96 + .../jackhuang/hmcl/download/VersionList.java} | 55 +- .../hmcl/download/forge/ForgeInstallTask.java | 140 ++ .../hmcl/download/forge/ForgeVersion.java | 91 + .../hmcl/download/forge/ForgeVersionList.java | 93 + .../hmcl/download/forge/ForgeVersionRoot.java | 102 + .../hmcl/download/forge/Install.java | 91 + .../hmcl/download/forge/InstallProfile.java | 60 + .../download/game/GameAssetDownloadTask.java | 111 ++ .../game/GameAssetIndexDownloadTask.java | 72 + .../download/game/GameAssetRefreshTask.java | 88 + .../hmcl/download/game/GameDownloadTask.java | 60 + .../hmcl/download/game/GameLibrariesTask.java | 70 + .../game/GameLoggingDownloadTask.java | 70 + .../game/GameRemoteLatestVersions.java | 52 + .../hmcl/download/game/GameRemoteVersion.java | 92 + .../download/game/GameRemoteVersionTag.java} | 45 +- .../download/game/GameRemoteVersions.java | 55 + .../hmcl/download/game/GameVersionList.java | 71 + .../game/VersionJsonDownloadTask.java | 70 + .../download/game/VersionJsonSaveTask.java | 56 + .../download/liteloader/LiteLoaderBranch.java | 57 + .../liteloader/LiteLoaderGameVersions.java | 61 + .../liteloader/LiteLoaderInstallTask.java | 111 ++ .../LiteLoaderRemoteVersionTag.java | 49 + .../liteloader/LiteLoaderRepository.java | 69 + .../liteloader/LiteLoaderVersion.java | 81 + .../liteloader/LiteLoaderVersionList.java | 91 + .../liteloader/LiteLoaderVersionsMeta.java | 61 + .../liteloader/LiteLoaderVersionsRoot.java | 55 + .../optifine/OptiFineBMCLVersionList.java | 79 + .../optifine/OptiFineDownloadFormatter.java | 56 + .../optifine/OptiFineInstallTask.java | 142 ++ .../download/optifine/OptiFineVersion.java | 90 + .../optifine/OptiFineVersionList.java | 96 + .../java/org/jackhuang/hmcl/event/Event.java | 85 + .../org/jackhuang/hmcl/event/EventBus.java | 43 + .../jackhuang/hmcl/event/EventManager.java | 85 + .../jackhuang/hmcl/event/EventPriority.java} | 10 +- .../org/jackhuang/hmcl/event/FailedEvent.java | 49 + .../hmcl/event/JVMLaunchFailedEvent.java} | 37 +- .../hmcl/event/LoadedOneVersionEvent.java | 44 + .../event/ProcessExitedAbnormallyEvent.java | 44 + .../hmcl/event/ProcessStoppedEvent.java | 44 + .../hmcl/event/RefreshedVersionsEvent.java | 37 + .../hmcl/event/RefreshingVersionsEvent.java | 37 + .../org/jackhuang/hmcl/game/Argument.java | 74 + .../org/jackhuang/hmcl/game/Arguments.java | 112 ++ .../org/jackhuang/hmcl/game/AssetIndex.java} | 45 +- .../jackhuang/hmcl/game/AssetIndexInfo.java | 55 + .../org/jackhuang/hmcl/game/AssetObject.java | 59 + .../hmcl/game/CircleDependencyException.java} | 22 +- .../jackhuang/hmcl/game/ClassicLibrary.java} | 10 +- .../jackhuang/hmcl/game/ClassicVersion.java | 54 + .../hmcl/game/CompatibilityRule.java | 85 + .../hmcl/game/DefaultGameRepository.java | 311 +++ .../org/jackhuang/hmcl/game/DownloadInfo.java | 75 + .../jackhuang/hmcl/game/DownloadType.java} | 12 +- .../org/jackhuang/hmcl/game/ExtractRules.java | 50 + .../jackhuang/hmcl/game/GameException.java | 40 + .../jackhuang/hmcl/game/GameRepository.java} | 62 +- .../org/jackhuang/hmcl/game/GameVersion.java | 129 ++ .../jackhuang/hmcl/game/IdDownloadInfo.java | 64 + .../jackhuang/hmcl/game/LaunchOptions.java | 303 +++ .../hmcl/game/LibrariesDownloadInfo.java | 52 + .../java/org/jackhuang/hmcl/game/Library.java | 203 ++ .../hmcl/game/LibraryDownloadInfo.java | 57 + .../org/jackhuang/hmcl/game/LoggingInfo.java | 76 + .../jackhuang/hmcl/game/OSRestriction.java | 79 + .../org/jackhuang/hmcl/game/ReleaseType.java} | 31 +- .../jackhuang/hmcl/game/RuledArgument.java | 110 ++ .../hmcl/game/SimpleVersionProvider.java | 47 + .../jackhuang/hmcl/game/StringArgument.java | 56 + .../java/org/jackhuang/hmcl/game/Version.java | 265 +++ .../hmcl/game/VersionNotFoundException.java | 36 + .../jackhuang/hmcl/game/VersionProvider.java} | 16 +- .../hmcl/launch/DefaultLauncher.java | 388 ++++ .../org/jackhuang/hmcl/launch/ExitWaiter.java | 88 + .../org/jackhuang/hmcl/launch/Launcher.java | 73 + .../jackhuang/hmcl/launch/Log4jHandler.java | 161 ++ .../hmcl/launch/ProcessListener.java} | 24 +- .../org/jackhuang/hmcl/launch/StreamPump.java | 68 + .../hmcl/mod/CurseCompletionTask.java | 121 ++ .../jackhuang/hmcl/mod/CurseInstallTask.java | 90 + .../org/jackhuang/hmcl/mod/CurseManifest.java | 129 ++ .../jackhuang/hmcl/mod/CurseManifestFile.java | 86 + .../hmcl/mod/CurseManifestMinecraft.java | 66 + .../hmcl/mod/CurseManifestModLoader.java | 62 + .../jackhuang/hmcl/mod/ForgeModMetadata.java | 139 ++ .../jackhuang/hmcl/mod/LiteModMetadata.java | 121 ++ .../java/org/jackhuang/hmcl/mod/ModInfo.java | 158 ++ .../org/jackhuang/hmcl/mod/ModManager.java | 79 + .../java/org/jackhuang/hmcl/mod/Modpack.java | 76 + .../mod/MultiMCInstanceConfiguration.java | 266 +++ .../hmcl/mod/MultiMCInstancePatch.java | 90 + .../hmcl/mod/MultiMCModpackInstallTask.java | 105 + .../org/jackhuang/hmcl/task/CoupleTask.java | 78 + .../jackhuang/hmcl/task/FileDownloadTask.java | 221 +++ .../java/org/jackhuang/hmcl/task/GetTask.java | 119 ++ .../org/jackhuang/hmcl/task/ParallelTask.java | 56 + .../org/jackhuang/hmcl/task/Scheduler.java | 37 + .../hmcl/task/SchedulerExecutorService.java | 41 + .../jackhuang/hmcl/task/SchedulerImpl.java | 93 + .../org/jackhuang/hmcl/task/Schedulers.java | 127 ++ .../jackhuang/hmcl/task/SilentException.java} | 13 +- .../org/jackhuang/hmcl/task/SimpleTask.java | 50 + .../java/org/jackhuang/hmcl/task/Task.java | 250 +++ .../org/jackhuang/hmcl/task/TaskCallable.java | 45 + .../jackhuang/hmcl/task/TaskCallable2.java | 46 + .../org/jackhuang/hmcl/task/TaskEvent.java} | 31 +- .../org/jackhuang/hmcl/task/TaskExecutor.java | 224 +++ .../org/jackhuang/hmcl/task/TaskListener.java | 44 + .../org/jackhuang/hmcl/task/TaskResult.java} | 27 +- .../jackhuang/hmcl/util/AutoTypingMap.java | 61 + .../org/jackhuang/hmcl/util/Charsets.java | 55 + .../jackhuang/hmcl/util/CompressingUtils.java | 237 +++ .../org/jackhuang/hmcl/util/Constants.java | 88 + .../jackhuang/hmcl/util/DateTypeAdapter.java | 96 + .../org/jackhuang/hmcl/util/DigestUtils.java | 251 +++ .../hmcl/util/ExceptionalFunction.java} | 10 +- .../hmcl/util/ExceptionalRunnable.java} | 31 +- .../hmcl/util/ExceptionalSupplier.java} | 15 +- .../jackhuang/hmcl/util/FileTypeAdapter.java | 58 + .../org/jackhuang/hmcl/util/FileUtils.java | 286 +++ .../java/org/jackhuang/hmcl/util/Hex.java | 128 ++ .../java/org/jackhuang/hmcl/util/IOUtils.java | 80 + .../hmcl/util/ImmediateBooleanProperty.java | 61 + .../hmcl/util/ImmediateDoubleProperty.java | 63 + .../hmcl/util/ImmediateIntegerProperty.java | 61 + .../hmcl/util/ImmediateObjectProperty.java | 61 + .../hmcl/util/ImmediateStringProperty.java | 61 + .../org/jackhuang/hmcl/util/Immutable.java} | 20 +- .../jackhuang/hmcl/util/IntVersionNumber.java | 75 + .../org/jackhuang/hmcl/util/JavaVersion.java | 270 +++ .../java/org/jackhuang/hmcl/util/Lang.java | 247 +++ .../org/jackhuang/hmcl/util/Log4jLevel.java | 141 ++ .../java/org/jackhuang/hmcl/util/Logging.java | 80 + .../util/LowerCaseEnumTypeAdapterFactory.java | 72 + .../jackhuang/hmcl/util/ManagedProcess.java | 137 ++ .../org/jackhuang/hmcl/util/NetworkUtils.java | 191 ++ .../jackhuang/hmcl/util/OperatingSystem.java | 124 ++ .../java/org/jackhuang/hmcl/util/Pair.java | 86 + .../org/jackhuang/hmcl/util/Platform.java | 102 + .../org/jackhuang/hmcl/util/Properties.java} | 30 +- .../jackhuang/hmcl/util/RandomUserAgent.java | 1664 ++++++++++++++++ .../jackhuang/hmcl/util/ReflectionHelper.java | 153 ++ .../jackhuang/hmcl/util/SimpleMultimap.java | 85 + .../org/jackhuang/hmcl/util/StringUtils.java | 205 ++ .../hmcl/util/StringVersionNumber.java | 70 + .../jackhuang/hmcl/util/UUIDTypeAdapter.java | 55 + .../org/jackhuang/hmcl/util/Validation.java | 40 + .../util/ValidationTypeAdapterFactory.java | 57 + .../jackhuang/hmcl/util/VersionNumber.java | 79 + .../org/jackhuang/hmcl/util/ZipEngine.java | 132 ++ .../kotlin/org/jackhuang/hmcl/auth/Account.kt | 28 - .../org/jackhuang/hmcl/auth/AccountFactory.kt | 23 - .../org/jackhuang/hmcl/auth/AuthInfo.kt | 40 - .../hmcl/auth/AuthenticationException.kt | 24 - .../org/jackhuang/hmcl/auth/OfflineAccount.kt | 74 - .../auth/yggdrasil/AuthenticationRequest.kt | 30 - .../hmcl/auth/yggdrasil/Exceptions.kt | 24 - .../hmcl/auth/yggdrasil/GameProfile.kt | 49 - .../hmcl/auth/yggdrasil/PropertyMap.kt | 83 - .../hmcl/auth/yggdrasil/RefreshRequest.kt | 25 - .../jackhuang/hmcl/auth/yggdrasil/Response.kt | 29 - .../org/jackhuang/hmcl/auth/yggdrasil/User.kt | 31 - .../hmcl/auth/yggdrasil/ValidateRequest.kt | 23 - .../hmcl/auth/yggdrasil/YggdrasilAccount.kt | 246 --- .../hmcl/download/BMCLAPIDownloadProvider.kt | 52 - .../hmcl/download/DefaultDependencyManager.kt | 66 - .../hmcl/download/DefaultGameBuilder.kt | 95 - .../hmcl/download/MojangDownloadProvider.kt | 52 - .../jackhuang/hmcl/download/RemoteVersion.kt | 55 - .../hmcl/download/forge/ForgeInstallTask.kt | 84 - .../hmcl/download/forge/ForgeRemote.kt | 84 - .../hmcl/download/forge/ForgeVersionList.kt | 72 - .../hmcl/download/game/GameDownloadTasks.kt | 174 -- .../hmcl/download/game/GameRemote.kt | 69 - .../hmcl/download/game/GameVersionList.kt | 59 - .../liteloader/LiteLoaderInstallTask.kt | 77 - .../download/liteloader/LiteLoaderRemote.kt | 94 - .../liteloader/LiteLoaderVersionList.kt | 73 - .../optifine/OptiFineBMCLVersionList.kt | 65 - .../optifine/OptiFineDownloadFormatter.kt | 21 - .../download/optifine/OptiFineInstallTask.kt | 86 - .../hmcl/download/optifine/OptiFineRemote.kt | 37 - .../download/optifine/OptiFineVersionList.kt | 85 - .../kotlin/org/jackhuang/hmcl/event/Event.kt | 60 - .../org/jackhuang/hmcl/event/EventManager.kt | 62 - .../kotlin/org/jackhuang/hmcl/event/Events.kt | 79 - .../org/jackhuang/hmcl/game/Argument.kt | 48 - .../org/jackhuang/hmcl/game/Arguments.kt | 65 - .../org/jackhuang/hmcl/game/AssetIndexInfo.kt | 29 - .../org/jackhuang/hmcl/game/ClassicVersion.kt | 59 - .../jackhuang/hmcl/game/CompatibilityRule.kt | 87 - .../hmcl/game/DefaultGameRepository.kt | 227 --- .../org/jackhuang/hmcl/game/DownloadInfo.kt | 54 - .../org/jackhuang/hmcl/game/DownloadType.kt | 29 - .../org/jackhuang/hmcl/game/ExtractRules.kt | 32 - .../org/jackhuang/hmcl/game/GameException.kt | 24 - .../org/jackhuang/hmcl/game/GameVersion.kt | 136 -- .../org/jackhuang/hmcl/game/LaunchOptions.kt | 128 -- .../hmcl/game/LibrariesDownloadInfo.kt | 38 - .../kotlin/org/jackhuang/hmcl/game/Library.kt | 114 -- .../hmcl/game/LibraryDownloadInfo.kt | 30 - .../org/jackhuang/hmcl/game/RuledArgument.kt | 54 - .../org/jackhuang/hmcl/game/StringArgument.kt | 36 - .../kotlin/org/jackhuang/hmcl/game/Version.kt | 194 -- .../hmcl/game/VersionNotFoundException.kt | 24 - .../jackhuang/hmcl/launch/DefaultLauncher.kt | 324 ---- .../org/jackhuang/hmcl/launch/ExitWaiter.kt | 62 - .../org/jackhuang/hmcl/launch/Launcher.kt | 44 - .../org/jackhuang/hmcl/launch/Log4jHandler.kt | 136 -- .../org/jackhuang/hmcl/launch/StreamPump.kt | 52 - .../jackhuang/hmcl/mod/CurseForgeModpack.kt | 214 --- .../jackhuang/hmcl/mod/ForgeModMetadata.kt | 74 - .../org/jackhuang/hmcl/mod/LiteModMetadata.kt | 63 - .../kotlin/org/jackhuang/hmcl/mod/ModInfo.kt | 90 - .../org/jackhuang/hmcl/mod/ModManager.kt | 69 - .../kotlin/org/jackhuang/hmcl/mod/Modpack.kt | 27 - .../org/jackhuang/hmcl/mod/MultiMCModpack.kt | 245 --- .../org/jackhuang/hmcl/task/CoupleTask.kt | 50 - .../jackhuang/hmcl/task/FileDownloadTask.kt | 167 -- .../kotlin/org/jackhuang/hmcl/task/GetTask.kt | 88 - .../org/jackhuang/hmcl/task/Scheduler.kt | 157 -- .../kotlin/org/jackhuang/hmcl/task/Task.kt | 147 -- .../org/jackhuang/hmcl/task/TaskEvent.kt | 22 - .../org/jackhuang/hmcl/task/TaskExecutor.kt | 166 -- .../org/jackhuang/hmcl/task/TaskListener.kt | 27 - .../org/jackhuang/hmcl/util/Compressors.kt | 177 -- .../org/jackhuang/hmcl/util/Constants.kt | 48 - .../org/jackhuang/hmcl/util/DigestUtils.kt | 242 --- .../org/jackhuang/hmcl/util/FileUtils.kt | 45 - .../kotlin/org/jackhuang/hmcl/util/Gson.kt | 217 --- .../kotlin/org/jackhuang/hmcl/util/Hex.kt | 121 -- .../kotlin/org/jackhuang/hmcl/util/IOUtils.kt | 93 - .../hmcl/util/ImmediateProperties.kt | 181 -- .../org/jackhuang/hmcl/util/JavaVersion.kt | 221 --- .../kotlin/org/jackhuang/hmcl/util/Logging.kt | 141 -- .../org/jackhuang/hmcl/util/ManagedProcess.kt | 77 - .../org/jackhuang/hmcl/util/NetUtils.kt | 152 -- .../main/kotlin/org/jackhuang/hmcl/util/OS.kt | 112 -- .../org/jackhuang/hmcl/util/Platform.kt | 70 - .../jackhuang/hmcl/util/RandomUserAgent.kt | 1683 ----------------- .../org/jackhuang/hmcl/util/SimpleMultimap.kt | 65 - .../org/jackhuang/hmcl/util/VersionNumber.kt | 113 -- .../org/jackhuang/hmcl/util/ZipEngine.kt | 126 -- 307 files changed, 17342 insertions(+), 10414 deletions(-) rename {HMCLCore => HMCL}/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt (54%) rename {HMCLCore => HMCL}/src/main/kotlin/org/jackhuang/hmcl/util/Lib.kt (100%) rename {HMCLCore => HMCL}/src/main/kotlin/org/jackhuang/hmcl/util/Properties.kt (100%) rename {HMCLCore => HMCL}/src/main/kotlin/org/jackhuang/hmcl/util/ReflectionHelper.kt (100%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/task/ParallelTask.kt => java/org/jackhuang/hmcl/auth/AccountFactory.java} (67%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthenticationException.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/UserType.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidCredentialsException.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/auth/UserType.kt => java/org/jackhuang/hmcl/auth/yggdrasil/InvalidTokenException.java} (64%) rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/task/SimpleTask.kt => java/org/jackhuang/hmcl/auth/yggdrasil/Property.java} (67%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/PropertyMap.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/RefreshRequest.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Response.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/User.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/task/TaskCallable.kt => java/org/jackhuang/hmcl/auth/yggdrasil/ValidateRequest.java} (61%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/download/AbstractDependencyManager.kt => java/org/jackhuang/hmcl/download/AbstractDependencyManager.java} (69%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/download/DependencyManager.kt => java/org/jackhuang/hmcl/download/DependencyManager.java} (78%) rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/download/DownloadProvider.kt => java/org/jackhuang/hmcl/download/DownloadProvider.java} (80%) rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/download/GameBuilder.kt => java/org/jackhuang/hmcl/download/GameBuilder.java} (59%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/download/VersionList.kt => java/org/jackhuang/hmcl/download/VersionList.java} (55%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/Install.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/InstallProfile.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetRefreshTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLoggingDownloadTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteLatestVersions.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersion.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/game/LoggingInfo.kt => java/org/jackhuang/hmcl/download/game/GameRemoteVersionTag.java} (55%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersions.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVersionList.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonDownloadTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonSaveTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBranch.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderGameVersions.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemoteVersionTag.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRepository.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersion.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsMeta.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsRoot.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineDownloadFormatter.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersion.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersionList.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/event/Event.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventBus.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventManager.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/event/EventPriority.kt => java/org/jackhuang/hmcl/event/EventPriority.java} (89%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/event/FailedEvent.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/event/EventBus.kt => java/org/jackhuang/hmcl/event/JVMLaunchFailedEvent.java} (53%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/event/LoadedOneVersionEvent.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessExitedAbnormallyEvent.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessStoppedEvent.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshedVersionsEvent.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshingVersionsEvent.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/Argument.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/game/AssetIndex.kt => java/org/jackhuang/hmcl/game/AssetIndex.java} (50%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndexInfo.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetObject.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/game/CircleDependencyException.kt => java/org/jackhuang/hmcl/game/CircleDependencyException.java} (70%) rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/mod/ModpackManager.kt => java/org/jackhuang/hmcl/game/ClassicLibrary.java} (88%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/CompatibilityRule.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/DownloadInfo.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/auth/yggdrasil/Property.kt => java/org/jackhuang/hmcl/game/DownloadType.java} (84%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/ExtractRules.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameException.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/game/GameRepository.kt => java/org/jackhuang/hmcl/game/GameRepository.java} (80%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/IdDownloadInfo.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibrariesDownloadInfo.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibraryDownloadInfo.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/LoggingInfo.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/OSRestriction.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/game/ReleaseType.kt => java/org/jackhuang/hmcl/game/ReleaseType.java} (72%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/SimpleVersionProvider.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/StringArgument.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionNotFoundException.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/game/VersionProvider.kt => java/org/jackhuang/hmcl/game/VersionProvider.java} (83%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Log4jHandler.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/launch/ProcessListener.kt => java/org/jackhuang/hmcl/launch/ProcessListener.java} (77%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/launch/StreamPump.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifest.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestFile.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestMinecraft.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestModLoader.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeModMetadata.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCInstanceConfiguration.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCInstancePatch.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/ParallelTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/SchedulerExecutorService.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/SchedulerImpl.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/util/Annotation.kt => java/org/jackhuang/hmcl/task/SilentException.java} (76%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/SimpleTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskCallable.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskCallable2.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/game/SimpleVersionProvider.kt => java/org/jackhuang/hmcl/task/TaskEvent.java} (62%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/launch/CrashReport.kt => java/org/jackhuang/hmcl/task/TaskResult.java} (70%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/AutoTypingMap.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Charsets.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/CompressingUtils.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/DateTypeAdapter.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/DigestUtils.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/event/ResultEvent.kt => java/org/jackhuang/hmcl/util/ExceptionalFunction.java} (82%) rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/game/AssetObject.kt => java/org/jackhuang/hmcl/util/ExceptionalRunnable.java} (65%) rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/task/SilentException.kt => java/org/jackhuang/hmcl/util/ExceptionalSupplier.java} (74%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileTypeAdapter.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Hex.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/IOUtils.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateBooleanProperty.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateDoubleProperty.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateIntegerProperty.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateObjectProperty.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateStringProperty.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/task/TaskResult.kt => java/org/jackhuang/hmcl/util/Immutable.java} (75%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/IntVersionNumber.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/JavaVersion.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Logging.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/LowerCaseEnumTypeAdapterFactory.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/ManagedProcess.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/OperatingSystem.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Pair.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Platform.java rename HMCLCore/src/main/{kotlin/org/jackhuang/hmcl/util/AutoTypingMap.kt => java/org/jackhuang/hmcl/util/Properties.java} (60%) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/RandomUserAgent.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/ReflectionHelper.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/SimpleMultimap.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringVersionNumber.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/UUIDTypeAdapter.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Validation.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/ValidationTypeAdapterFactory.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/VersionNumber.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/ZipEngine.java delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/Account.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AccountFactory.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AuthInfo.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AuthenticationException.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/OfflineAccount.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Exceptions.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/PropertyMap.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/RefreshRequest.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Response.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/User.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/ValidateRequest.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultDependencyManager.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultGameBuilder.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/MojangDownloadProvider.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/RemoteVersion.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeInstallTask.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeRemote.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeVersionList.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameDownloadTasks.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameRemote.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameVersionList.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemote.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineDownloadFormatter.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineRemote.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineVersionList.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/Event.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventManager.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/Events.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Argument.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Arguments.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetIndexInfo.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ClassicVersion.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/CompatibilityRule.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DefaultGameRepository.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DownloadInfo.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DownloadType.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ExtractRules.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameException.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameVersion.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LaunchOptions.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LibrariesDownloadInfo.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Library.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LibraryDownloadInfo.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/RuledArgument.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/StringArgument.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Version.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/VersionNotFoundException.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/ExitWaiter.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/Launcher.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/Log4jHandler.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/StreamPump.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ForgeModMetadata.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/LiteModMetadata.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModInfo.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModManager.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/CoupleTask.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/FileDownloadTask.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Scheduler.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskEvent.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskListener.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Constants.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/DigestUtils.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/FileUtils.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Gson.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Hex.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/IOUtils.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ImmediateProperties.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/JavaVersion.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Logging.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ManagedProcess.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/NetUtils.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/OS.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Platform.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/RandomUserAgent.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/SimpleMultimap.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/VersionNumber.kt delete mode 100644 HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ZipEngine.kt diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt index c2abc6373..cc668ec9d 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/Main.kt @@ -22,11 +22,13 @@ import javafx.application.Platform import javafx.stage.Stage import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.Schedulers import org.jackhuang.hmcl.ui.Controllers import org.jackhuang.hmcl.ui.runOnUiThread -import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT -import org.jackhuang.hmcl.util.LOG -import org.jackhuang.hmcl.util.OS +import org.jackhuang.hmcl.util.Constants +import org.jackhuang.hmcl.util.Logging.LOG +import org.jackhuang.hmcl.util.NetworkUtils +import org.jackhuang.hmcl.util.OperatingSystem import java.io.File import java.util.logging.Level @@ -34,7 +36,7 @@ fun i18n(key: String): String { try { return Main.RESOURCE_BUNDLE.getString(key) } catch (e: Exception) { - LOG.log(Level.WARNING, "Cannot find key $key in resource bundle", e) + LOG.log(Level.SEVERE, "Cannot find key $key in resource bundle", e) return key } } @@ -62,20 +64,21 @@ class Main : Application() { @JvmStatic fun main(args: Array) { - DEFAULT_USER_AGENT = { "Hello Minecraft! Launcher" } + NetworkUtils.setUserAgentSupplier { "Hello Minecraft! Launcher" } + Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER; launch(Main::class.java, *args) } fun getWorkingDirectory(folder: String): File { val userhome = System.getProperty("user.home", ".") - return when (OS.CURRENT_OS) { - OS.LINUX -> File(userhome, ".$folder/") - OS.WINDOWS -> { + return when (OperatingSystem.CURRENT_OS) { + OperatingSystem.LINUX -> File(userhome, ".$folder/") + OperatingSystem.WINDOWS -> { val appdata: String? = System.getenv("APPDATA") File(appdata ?: userhome, ".$folder/") } - OS.OSX -> File(userhome, "Library/Application Support/" + folder) + OperatingSystem.OSX -> File(userhome, "Library/Application Support/" + folder) else -> File(userhome, "$folder/") } } @@ -89,7 +92,7 @@ class Main : Application() { fun stopWithoutJavaFXPlatform() = runOnUiThread { Controllers.stage.close() - Scheduler.shutdown() + Schedulers.shutdown() } val RESOURCE_BUNDLE = Settings.locale.resourceBundle diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/AccountHelper.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/AccountHelper.kt index c64caae52..f8e58ff8b 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/AccountHelper.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/AccountHelper.kt @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.FileDownloadTask import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.Schedulers import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.ui.DEFAULT_ICON import org.jackhuang.hmcl.ui.DialogController @@ -48,11 +49,13 @@ object AccountHelper { SkinLoadTask(account, proxy, true) private class SkinLoadTask(val account: YggdrasilAccount, val proxy: Proxy, val refresh: Boolean = false): Task() { - override val scheduler = Scheduler.IO - override val dependencies = mutableListOf() + + override fun getScheduler() = Schedulers.io() + private val dependencies = mutableListOf() + override fun getDependencies() = dependencies override fun execute() { - if (account.canLogIn && (account.selectedProfile == null || refresh)) + if (account.canLogIn() && (account.selectedProfile == null || refresh)) DialogController.logIn(account) val profile = account.selectedProfile ?: return val name = profile.name ?: return @@ -60,7 +63,7 @@ object AccountHelper { val file = getSkinFile(name) if (!refresh && file.exists()) return - dependencies += FileDownloadTask(url.toURL(), file, proxy = proxy) + dependencies += FileDownloadTask(url.toURL(), file, proxy) } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLGameRepository.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLGameRepository.kt index 4d8f3b552..ec0455c1a 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLGameRepository.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLGameRepository.kt @@ -23,8 +23,8 @@ import org.jackhuang.hmcl.setting.EnumGameDirectory import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.VersionSetting -import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.util.LOG +import org.jackhuang.hmcl.task.Schedulers +import org.jackhuang.hmcl.util.Logging.LOG import org.jackhuang.hmcl.util.fromJson import java.io.File import java.io.IOException @@ -72,7 +72,7 @@ class HMCLGameRepository(val profile: Profile, baseDirectory: File) @Synchronized override fun refreshVersionsImpl() { - Scheduler.NEW_THREAD.schedule { + Schedulers.newThread().schedule { versionSettings.clear() super.refreshVersionsImpl() diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt index 3aacca13c..fbdb3f97a 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt @@ -18,7 +18,8 @@ package org.jackhuang.hmcl.game import com.google.gson.JsonParseException -import org.jackhuang.hmcl.download.game.VersionJSONSaveTask +import org.jackhuang.hmcl.download.game.VersionJsonSaveTask +import org.jackhuang.hmcl.game.GameVersion.minecraftVersion import org.jackhuang.hmcl.mod.Modpack import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.task.Task @@ -26,6 +27,8 @@ import java.io.File import java.io.IOException import org.jackhuang.hmcl.task.TaskResult import org.jackhuang.hmcl.util.* +import org.jackhuang.hmcl.util.Constants.GSON +import org.jackhuang.hmcl.util.Logging.LOG import java.util.ArrayList /** @@ -38,39 +41,42 @@ import java.util.ArrayList */ @Throws(IOException::class, JsonParseException::class) fun readHMCLModpackManifest(f: File): Modpack { - val manifestJson = f.readTextZipEntry("modpack.json") + val manifestJson = CompressingUtils.readTextZipEntry(f, "modpack.json") val manifest = GSON.fromJson(manifestJson) ?: throw JsonParseException("`modpack.json` not found. $f is not a valid HMCL modpack.") - val gameJson = f.readTextZipEntry("minecraft/pack.json") + val gameJson = CompressingUtils.readTextZipEntry(f, "minecraft/pack.json") val game = GSON.fromJson(gameJson) ?: throw JsonParseException("`minecraft/pack.json` not found. $f iot a valid HMCL modpack.") return if (game.jar == null) if (manifest.gameVersion.isNullOrBlank()) throw JsonParseException("Cannot recognize the game version of modpack $f.") - else manifest.copy(manifest = HMCLModpackManifest) - else manifest.copy(manifest = HMCLModpackManifest, gameVersion = game.jar!!) + else manifest.setManifest(HMCLModpackManifest) + else manifest.setManifest(HMCLModpackManifest).setGameVersion(game.jar) } object HMCLModpackManifest -class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, modpack: Modpack, private val name: String): Task() { +class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, modpack: Modpack, private val version: String): Task() { private val dependency = profile.dependency private val repository = profile.repository - override val dependencies = mutableListOf() - override val dependents = mutableListOf() + private val dependencies = mutableListOf() + private val dependents = mutableListOf() + + override fun getDependencies() = dependencies + override fun getDependents() = dependents init { - check(!repository.hasVersion(name), { "Version $name already exists." }) - val json = zipFile.readTextZipEntry("minecraft/pack.json") + check(!repository.hasVersion(version), { "Version $version already exists." }) + val json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json") var version = GSON.fromJson(json)!! - version = version.copy(jar = null) - dependents += dependency.gameBuilder().name(name).gameVersion(modpack.gameVersion!!).buildAsync() - dependencies += VersionJSONSaveTask(repository, version) // override the json created by buildAsync() + version = version.setJar(null) + dependents += dependency.gameBuilder().name(this.version).gameVersion(modpack.gameVersion!!).buildAsync() + dependencies += VersionJsonSaveTask(repository, version) // override the json created by buildAsync() - onDone += { event -> if (event.failed) repository.removeVersionFromDisk(name) } + onDone() += { event -> if (event.isFailed) repository.removeVersionFromDisk(this.version) } } - private val run = repository.getRunDirectory(name) + private val run = repository.getRunDirectory(version) override fun execute() { - zipFile.uncompressTo(run, "minecraft/", callback = { it != "minecraft/pack.json" }, ignoreExistentFile = false) + CompressingUtils.unzip(zipFile, run, "minecraft/", { it != "minecraft/pack.json" }, false) } } @@ -120,10 +126,12 @@ class HMCLModpackExportTask @JvmOverloads constructor( private val whitelist: List, private val modpack: Modpack, private val output: File, - override val id: String = ID): TaskResult() { + private val id: String = ID): TaskResult() { + + override fun getId() = id init { - onDone += { event -> if (event.failed) output.delete() } + onDone() += { event -> if (event.isFailed) output.delete() } } override fun execute() { @@ -148,8 +156,8 @@ class HMCLModpackExportTask @JvmOverloads constructor( val mv = repository.getVersion(version).resolve(repository) val gameVersion = minecraftVersion(repository.getVersionJar(version)) ?: throw IllegalStateException("Cannot parse the version of $version") - zip.putTextFile(GSON.toJson(mv.copy(jar = gameVersion)), "minecraft/pack.json") // Making "jar" to gameVersion is to be compatible with old HMCL. - zip.putTextFile(GSON.toJson(modpack.copy(gameVersion = gameVersion)), "modpack.json") // Newer HMCL only reads 'gameVersion' field. + zip.putTextFile(GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json") // Making "jar" to gameVersion is to be compatible with old HMCL. + zip.putTextFile(GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json") // Newer HMCL only reads 'gameVersion' field. } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt index 9f85e5792..5915a8e5c 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt @@ -23,7 +23,7 @@ import org.jackhuang.hmcl.auth.AuthInfo import org.jackhuang.hmcl.auth.AuthenticationException import org.jackhuang.hmcl.launch.DefaultLauncher import org.jackhuang.hmcl.launch.ProcessListener -import org.jackhuang.hmcl.mod.CurseForgeModpackCompletionTask +import org.jackhuang.hmcl.mod.CurseCompletionTask import org.jackhuang.hmcl.setting.LauncherVisibility import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.VersionSetting @@ -31,10 +31,10 @@ import org.jackhuang.hmcl.task.* import org.jackhuang.hmcl.ui.* import org.jackhuang.hmcl.util.Log4jLevel import org.jackhuang.hmcl.util.ManagedProcess +import org.jackhuang.hmcl.util.task import java.util.* import java.util.concurrent.ConcurrentLinkedQueue - object LauncherHelper { private val launchingStepsPane = LaunchingStepsPane() val PROCESS = ConcurrentLinkedQueue() @@ -49,13 +49,13 @@ object LauncherHelper { val setting = profile.getVersionSetting(selectedVersion) Controllers.dialog(launchingStepsPane) - task(Scheduler.JAVAFX) { emitStatus(LoadingState.DEPENDENCIES) } + task(Schedulers.javafx()) { emitStatus(LoadingState.DEPENDENCIES) } .then(dependency.checkGameCompletionAsync(version)) - .then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.MODS) }) - .then(CurseForgeModpackCompletionTask(dependency, selectedVersion)) + .then(task(Schedulers.javafx()) { emitStatus(LoadingState.MODS) }) + .then(CurseCompletionTask(dependency, selectedVersion)) - .then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.LOGIN) }) + .then(task(Schedulers.javafx()) { emitStatus(LoadingState.LOGIN) }) .then(task { try { it["account"] = account.logIn(Settings.proxy) @@ -65,7 +65,7 @@ object LauncherHelper { } }) - .then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.LAUNCHING) }) + .then(task(Schedulers.javafx()) { emitStatus(LoadingState.LAUNCHING) }) .then(task { it["launcher"] = HMCLGameLauncher( repository = repository, @@ -84,12 +84,12 @@ object LauncherHelper { .executor() .apply { - taskListener = object : TaskListener { + taskListener = object : TaskListener() { var finished = 0 override fun onFinished(task: Task) { ++finished - runOnUiThread { launchingStepsPane.pgsTasks.progress = 1.0 * finished / totTask.get() } + runOnUiThread { launchingStepsPane.pgsTasks.progress = 1.0 * finished / runningTasks } } override fun onTerminate() { @@ -179,9 +179,14 @@ object LauncherHelper { if (exitType != ProcessListener.ExitType.NORMAL && logWindow == null){ runOnUiThread { LogWindow().apply { - for ((line, level) in logs) - logLine(line, level) show() + Schedulers.newThread().schedule { + waitForShown() + runOnUiThread { + for ((line, level) in logs) + logLine(line, level) + } + } } } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt index 3aa35fffc..c5a7c4cbe 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt @@ -17,21 +17,18 @@ */ package org.jackhuang.hmcl.game -import org.jackhuang.hmcl.mod.InstanceConfiguration -import org.jackhuang.hmcl.mod.Modpack -import org.jackhuang.hmcl.mod.readCurseForgeModpackManifest -import org.jackhuang.hmcl.mod.readMMCModpackManifest +import org.jackhuang.hmcl.mod.* import org.jackhuang.hmcl.setting.EnumGameDirectory import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.VersionSetting -import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.Schedulers import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.util.toStringOrEmpty import java.io.File fun readModpackManifest(f: File): Modpack { try { - return readCurseForgeModpackManifest(f) + return CurseManifest.readCurseForgeModpackManifest(f); } catch (e: Exception) { // ignore it, not a valid CurseForge modpack. } @@ -44,7 +41,7 @@ fun readModpackManifest(f: File): Modpack { } try { - val manifest = readMMCModpackManifest(f) + val manifest = MultiMCInstanceConfiguration.readMultiMCModpackManifest(f) return manifest } catch (e: Exception) { // ignore it, not a valid MMC modpack. @@ -53,36 +50,36 @@ fun readModpackManifest(f: File): Modpack { throw IllegalArgumentException("Modpack file $f is not supported.") } -fun InstanceConfiguration.toVersionSetting(vs: VersionSetting) { +fun MultiMCInstanceConfiguration.toVersionSetting(vs: VersionSetting) { vs.usesGlobal = false vs.gameDirType = EnumGameDirectory.VERSION_FOLDER - if (overrideJavaLocation) { + if (isOverrideJavaLocation) { vs.javaDir = javaPath.toStringOrEmpty() } - if (overrideMemory) { + if (isOverrideMemory) { vs.permSize = permGen.toStringOrEmpty() if (maxMemory != null) vs.maxMemory = maxMemory!! vs.minMemory = minMemory } - if (overrideCommands) { + if (isOverrideCommands) { vs.wrapper = wrapperCommand.orEmpty() vs.precalledCommand = preLaunchCommand.orEmpty() } - if (overrideJavaArgs) { + if (isOverrideJavaArgs) { vs.javaArgs = jvmArgs.orEmpty() } - if (overrideConsole) { - vs.showLogs = showConsole + if (isOverrideConsole) { + vs.showLogs = isShowConsole } - if (overrideWindow) { - vs.fullscreen = fullscreen + if (isOverrideWindow) { + vs.fullscreen = isFullscreen if (width != null) vs.width = width!! if (height != null) @@ -90,10 +87,10 @@ fun InstanceConfiguration.toVersionSetting(vs: VersionSetting) { } } -class MMCInstallVersionSettingTask(private val profile: Profile, val manifest: InstanceConfiguration, val name: String): Task() { - override val scheduler = Scheduler.JAVAFX +class MMCInstallVersionSettingTask(private val profile: Profile, val manifest: MultiMCInstanceConfiguration, private val version: String): Task() { + override fun getScheduler() = Schedulers.javafx() override fun execute() { - val vs = profile.specializeVersionSetting(name)!! + val vs = profile.specializeVersionSetting(version)!! manifest.toVersionSetting(vs) } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt index cd7115694..e7cb40e30 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt @@ -20,7 +20,7 @@ package org.jackhuang.hmcl.setting import com.google.gson.* import javafx.beans.InvalidationListener import org.jackhuang.hmcl.download.DefaultDependencyManager -import org.jackhuang.hmcl.event.EVENT_BUS +import org.jackhuang.hmcl.event.EventBus import org.jackhuang.hmcl.event.RefreshedVersionsEvent import org.jackhuang.hmcl.game.HMCLGameRepository import org.jackhuang.hmcl.mod.ModManager @@ -49,7 +49,7 @@ class Profile(name: String = "Default", initialGameDir: File = File(".minecraft" init { gameDirProperty.onChange { repository.changeDirectory(it!!) } selectedVersionProperty.onInvalidated(this::verifySelectedVersion) - EVENT_BUS.channel() += { event -> if (event.source == repository) verifySelectedVersion() } + EventBus.EVENT_BUS.channel().register { event -> if (event.source == repository) verifySelectedVersion() } } private fun verifySelectedVersion() = runOnUiThread { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt index c9ac41b27..470f9c096 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt @@ -27,9 +27,10 @@ import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider import org.jackhuang.hmcl.download.DownloadProvider import org.jackhuang.hmcl.download.MojangDownloadProvider -import org.jackhuang.hmcl.event.EVENT_BUS -import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.event.EventBus +import org.jackhuang.hmcl.task.Schedulers import org.jackhuang.hmcl.util.* +import org.jackhuang.hmcl.util.Logging.LOG import java.io.File import java.io.IOException import java.net.Authenticator @@ -43,7 +44,7 @@ object Settings { val GSON = GsonBuilder() .registerTypeAdapter(VersionSetting::class.java, VersionSetting) .registerTypeAdapter(Profile::class.java, Profile) - .registerTypeAdapter(File::class.java, FileTypeAdapter) + .registerTypeAdapter(File::class.java, FileTypeAdapter.INSTANCE) .setPrettyPrinting().create() const val DEFAULT_PROFILE = "Default" @@ -209,14 +210,14 @@ object Settings { var downloadProvider: DownloadProvider get() = when (SETTINGS.downloadtype) { - 0 -> MojangDownloadProvider - 1 -> BMCLAPIDownloadProvider - else -> MojangDownloadProvider + 0 -> MojangDownloadProvider.INSTANCE + 1 -> BMCLAPIDownloadProvider.INSTANCE + else -> MojangDownloadProvider.INSTANCE } set(value) { SETTINGS.downloadtype = when (value) { - MojangDownloadProvider -> 0 - BMCLAPIDownloadProvider -> 1 + MojangDownloadProvider.INSTANCE -> 0 + BMCLAPIDownloadProvider.INSTANCE -> 1 else -> 0 } } @@ -275,14 +276,14 @@ object Settings { get() { if (!hasProfile(SETTINGS.selectedProfile)) { SETTINGS.selectedProfile = DEFAULT_PROFILE - Scheduler.COMPUTATION.schedule { onProfileChanged() } + Schedulers.computation().schedule { onProfileChanged() } } return getProfile(SETTINGS.selectedProfile) } set(value) { if (hasProfile(value.name) && value.name != SETTINGS.selectedProfile) { SETTINGS.selectedProfile = value.name - Scheduler.COMPUTATION.schedule { onProfileChanged() } + Schedulers.computation().schedule { onProfileChanged() } } } @@ -327,14 +328,14 @@ object Settings { } val flag = getProfileMap().remove(ver) != null if (flag) - Scheduler.COMPUTATION.schedule { onProfileLoading() } + Schedulers.computation().schedule { onProfileLoading() } return flag } internal fun onProfileChanged() { selectedProfile.repository.refreshVersions() - EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile)) + EventBus.EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile)) } /** @@ -342,7 +343,7 @@ object Settings { * Invoked by loading GUI phase. */ fun onProfileLoading() { - EVENT_BUS.fireEvent(ProfileLoadingEvent(SETTINGS)) + EventBus.EVENT_BUS.fireEvent(ProfileLoadingEvent(SETTINGS)) onProfileChanged() } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/SettingsConstants.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/SettingsConstants.kt index 4533a747e..c92c4e3b4 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/SettingsConstants.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/SettingsConstants.kt @@ -21,7 +21,9 @@ import javafx.scene.text.Font import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.auth.AccountFactory import org.jackhuang.hmcl.auth.OfflineAccount +import org.jackhuang.hmcl.auth.OfflineAccountFactory import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider import org.jackhuang.hmcl.download.MojangDownloadProvider import org.jackhuang.hmcl.ui.construct.UTF8Control @@ -35,19 +37,18 @@ object Proxies { } object DownloadProviders { - val DOWNLOAD_PROVIDERS = listOf(MojangDownloadProvider, BMCLAPIDownloadProvider) + val DOWNLOAD_PROVIDERS = listOf(MojangDownloadProvider.INSTANCE, BMCLAPIDownloadProvider.INSTANCE) - fun getDownloadProvider(index: Int) = DOWNLOAD_PROVIDERS.getOrElse(index, { MojangDownloadProvider }) + fun getDownloadProvider(index: Int) = DOWNLOAD_PROVIDERS.getOrElse(index, { MojangDownloadProvider.INSTANCE }) } object Accounts { val OFFLINE_ACCOUNT_KEY = "offline" val YGGDRASIL_ACCOUNT_KEY = "yggdrasil" - val ACCOUNTS = listOf(OfflineAccount, YggdrasilAccount) val ACCOUNT_FACTORY = mapOf>( - OFFLINE_ACCOUNT_KEY to OfflineAccount, - YGGDRASIL_ACCOUNT_KEY to YggdrasilAccount + OFFLINE_ACCOUNT_KEY to OfflineAccountFactory.INSTANCE, + YGGDRASIL_ACCOUNT_KEY to YggdrasilAccountFactory.INSTANCE ) fun getAccountType(account: Account): String { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt index b86d9f43b..aae249f4c 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt @@ -21,6 +21,10 @@ import com.google.gson.* import javafx.beans.InvalidationListener import org.jackhuang.hmcl.Main import org.jackhuang.hmcl.game.LaunchOptions +import org.jackhuang.hmcl.setting.Settings.proxyHost +import org.jackhuang.hmcl.setting.Settings.proxyPass +import org.jackhuang.hmcl.setting.Settings.proxyPort +import org.jackhuang.hmcl.setting.Settings.proxyUser import org.jackhuang.hmcl.util.* import java.io.File import java.io.IOException @@ -70,7 +74,7 @@ class VersionSetting() { /** * The maximum memory that JVM can allocate for heap. */ - val maxMemoryProperty = ImmediateIntegerProperty(this, "maxMemory", OS.SUGGESTED_MEMORY) + val maxMemoryProperty = ImmediateIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY.toInt()) var maxMemory: Int by maxMemoryProperty /** @@ -232,28 +236,28 @@ class VersionSetting() { @Throws(IOException::class) fun toLaunchOptions(gameDir: File): LaunchOptions { - return LaunchOptions( - gameDir = gameDir, - java = javaVersion ?: JavaVersion.fromCurrentEnvironment(), - versionName = Main.TITLE, - profileName = Main.TITLE, - minecraftArgs = minecraftArgs, - javaArgs = javaArgs, - maxMemory = maxMemory, - minMemory = minMemory, - metaspace = permSize.toIntOrNull(), - width = width, - height = height, - fullscreen = fullscreen, - serverIp = serverIp, - wrapper = wrapper, - proxyHost = Settings.proxyHost, - proxyPort = Settings.proxyPort, - proxyUser = Settings.proxyUser, - proxyPass = Settings.proxyPass, - precalledCommand = precalledCommand, - noGeneratedJVMArgs = noJVMArgs - ) + return LaunchOptions.Builder() + .setGameDir(gameDir) + .setJava(javaVersion ?: JavaVersion.fromCurrentEnvironment()) + .setVersionName(Main.TITLE) + .setProfileName(Main.TITLE) + .setMinecraftArgs(minecraftArgs) + .setJavaArgs(javaArgs) + .setMaxMemory(maxMemory) + .setMinMemory(minMemory) + .setMetaspace(permSize.toIntOrNull()) + .setWidth(width) + .setHeight(height) + .setFullscreen(fullscreen) + .setServerIp(serverIp) + .setWrapper(wrapper) + .setProxyHost(Settings.proxyHost) + .setProxyPort(Settings.proxyPort) + .setProxyUser(Settings.proxyUser) + .setProxyPass(Settings.proxyPass) + .setPrecalledCommand(precalledCommand) + .setNoGeneratedJVMArgs(noJVMArgs) + .create() } companion object Serializer: JsonSerializer, JsonDeserializer { @@ -264,7 +268,7 @@ class VersionSetting() { addProperty("usesGlobal", src.usesGlobal) addProperty("javaArgs", src.javaArgs) addProperty("minecraftArgs", src.minecraftArgs) - addProperty("maxMemory", if (src.maxMemory <= 0) OS.SUGGESTED_MEMORY else src.maxMemory) + addProperty("maxMemory", if (src.maxMemory <= 0) OperatingSystem.SUGGESTED_MEMORY.toInt() else src.maxMemory) addProperty("minMemory", src.minMemory) addProperty("permSize", src.permSize) addProperty("width", src.width) @@ -290,8 +294,8 @@ class VersionSetting() { override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): VersionSetting? { if (json == null || json == JsonNull.INSTANCE || json !is JsonObject) return null - var maxMemoryN = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive, OS.SUGGESTED_MEMORY) - if (maxMemoryN <= 0) maxMemoryN = OS.SUGGESTED_MEMORY + var maxMemoryN = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive, OperatingSystem.SUGGESTED_MEMORY.toInt()) + if (maxMemoryN <= 0) maxMemoryN = OperatingSystem.SUGGESTED_MEMORY.toInt() return VersionSetting().apply { usesGlobal = json["usesGlobal"]?.asBoolean ?: false diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt index af2bbb7da..a7ad975b8 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt @@ -39,6 +39,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.game.AccountHelper import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.Schedulers import java.util.concurrent.Callable class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane() { @@ -83,10 +84,10 @@ class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane( btnRefresh.setOnMouseClicked { pgsSkin.isVisible = true AccountHelper.refreshSkinAsync(account) - .subscribe(Scheduler.JAVAFX) { loadSkin() } + .subscribe(Schedulers.javafx()) { loadSkin() } } AccountHelper.loadSkinAsync(account) - .subscribe(Scheduler.JAVAFX) { loadSkin() } + .subscribe(Schedulers.javafx()) { loadSkin() } } if (account is OfflineAccount) { // Offline Account cannot be refreshed, diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt index b812f7358..5245cd95a 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt @@ -29,15 +29,17 @@ import javafx.scene.control.ToggleGroup import javafx.scene.layout.StackPane import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.auth.OfflineAccount +import org.jackhuang.hmcl.auth.OfflineAccountFactory import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.Settings -import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.task.taskResult +import org.jackhuang.hmcl.task.Schedulers import org.jackhuang.hmcl.ui.wizard.DecoratorPage import org.jackhuang.hmcl.util.onChange import org.jackhuang.hmcl.util.onChangeAndOperate +import org.jackhuang.hmcl.util.taskResult class AccountsPage() : StackPane(), DecoratorPage { override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts") @@ -126,8 +128,8 @@ class AccountsPage() : StackPane(), DecoratorPage { taskResult("create_account") { try { val account = when (type) { - 0 -> OfflineAccount.fromUsername(username) - 1 -> YggdrasilAccount.fromUsername(username, password) + 0 -> OfflineAccountFactory.INSTANCE.fromUsername(username) + 1 -> YggdrasilAccountFactory.INSTANCE.fromUsername(username, password) else -> throw UnsupportedOperationException() } @@ -136,7 +138,7 @@ class AccountsPage() : StackPane(), DecoratorPage { } catch (e: Exception) { e } - }.subscribe(Scheduler.JAVAFX) { + }.subscribe(Schedulers.javafx()) { val account: Any = it["create_account"] if (account is Account) { Settings.addAccount(account) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt index 8e9f2d30f..431652b7b 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt @@ -25,8 +25,8 @@ import javafx.scene.layout.Region import javafx.stage.Stage import org.jackhuang.hmcl.Main import org.jackhuang.hmcl.setting.Settings -import org.jackhuang.hmcl.task.task import org.jackhuang.hmcl.util.JavaVersion +import org.jackhuang.hmcl.util.task object Controllers { lateinit var scene: Scene private set diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt index f9730e748..27da90592 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt @@ -43,6 +43,7 @@ import javafx.scene.shape.Rectangle import javafx.util.Duration import org.jackhuang.hmcl.Main import org.jackhuang.hmcl.util.* +import org.jackhuang.hmcl.util.Logging.LOG import java.io.File import java.io.IOException import java.util.logging.Level @@ -196,8 +197,8 @@ fun JFXMasonryPane.resetChildren(children: List) { fun openFolder(f: File) { f.mkdirs() val path = f.absolutePath - when (OS.CURRENT_OS) { - OS.OSX -> + when (OperatingSystem.CURRENT_OS) { + OperatingSystem.OSX -> try { Runtime.getRuntime().exec(arrayOf("/usr/bin/open", path)); } catch (ex: IOException) { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerController.kt index d2ff528b9..9d82ffab8 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/InstallerController.kt @@ -20,13 +20,13 @@ package org.jackhuang.hmcl.ui import javafx.fxml.FXML import javafx.scene.control.ScrollPane import javafx.scene.layout.VBox -import org.jackhuang.hmcl.download.game.VersionJSONSaveTask +import org.jackhuang.hmcl.download.game.VersionJsonSaveTask +import org.jackhuang.hmcl.game.GameVersion.minecraftVersion import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.game.minecraftVersion import org.jackhuang.hmcl.setting.Profile -import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.task.task +import org.jackhuang.hmcl.task.Schedulers import org.jackhuang.hmcl.ui.download.InstallWizardProvider +import org.jackhuang.hmcl.util.task import java.util.* class InstallerController { @@ -59,9 +59,9 @@ class InstallerController { val removeAction = { _: InstallerItem -> val newList = LinkedList(version.libraries) newList.remove(library) - VersionJSONSaveTask(profile.repository, version.copy(libraries = newList)) + VersionJsonSaveTask(profile.repository, version.setLibraries(newList)) .with(task { profile.repository.refreshVersions() }) - .with(task(Scheduler.JAVAFX) { loadVersion(this.profile, this.versionId) }) + .with(task(Schedulers.javafx()) { loadVersion(this.profile, this.versionId) }) .start() } if (library.groupId.equals("net.minecraftforge", ignoreCase = true) && library.artifactId.equals("forge", ignoreCase = true)) { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt index 3e383404c..f640f0509 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt @@ -22,13 +22,15 @@ import javafx.scene.paint.Paint import org.jackhuang.hmcl.ProfileChangedEvent import org.jackhuang.hmcl.ProfileLoadingEvent import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount -import org.jackhuang.hmcl.event.EVENT_BUS +import org.jackhuang.hmcl.event.EventBus import org.jackhuang.hmcl.game.AccountHelper import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.ui.construct.IconedItem import org.jackhuang.hmcl.ui.construct.RipplerContainer +import org.jackhuang.hmcl.util.channel import org.jackhuang.hmcl.util.onChangeAndOperate +import org.jackhuang.hmcl.util.plusAssign import java.util.* class LeftPaneController(private val leftPane: AdvancedListBox) { @@ -56,8 +58,8 @@ class LeftPaneController(private val leftPane: AdvancedListBox) { .startCategory(i18n("ui.label.profile")) .add(profilePane) - EVENT_BUS.channel() += this::onProfilesLoading - EVENT_BUS.channel() += this::onProfileChanged + EventBus.EVENT_BUS.channel() += this::onProfilesLoading + EventBus.EVENT_BUS.channel() += this::onProfileChanged Controllers.decorator.addMenuButton.setOnMouseClicked { Controllers.decorator.showPage(ProfilePage(null)) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LogWindow.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LogWindow.kt index 1b7c745fd..b3a3d6cec 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LogWindow.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LogWindow.kt @@ -38,6 +38,7 @@ import org.jackhuang.hmcl.util.* import org.w3c.dom.Document import org.w3c.dom.Node import java.util.concurrent.Callable +import java.util.concurrent.CountDownLatch class LogWindow : Stage() { val fatalProperty = SimpleIntegerProperty(0) @@ -47,6 +48,7 @@ class LogWindow : Stage() { val debugProperty = SimpleIntegerProperty(0) val impl = LogWindowImpl() + val latch = CountDownLatch(1) init { scene = Scene(impl, 800.0, 480.0) @@ -74,6 +76,8 @@ class LogWindow : Stage() { } } + fun waitForShown() = latch.await() + inner class LogWindowImpl: StackPane() { @FXML lateinit var webView: WebView @FXML lateinit var btnFatals: ToggleButton @@ -97,6 +101,7 @@ class LogWindow : Stage() { document = engine.document body = document.getElementsByTagName("body").item(0) engine.executeScript("limitedLogs=${Settings.logLines};") + latch.countDown() } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt index 08e6215a0..aff8d3b14 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt @@ -29,17 +29,19 @@ import javafx.scene.image.Image import javafx.scene.layout.StackPane import org.jackhuang.hmcl.ProfileChangedEvent import org.jackhuang.hmcl.ProfileLoadingEvent -import org.jackhuang.hmcl.event.EVENT_BUS +import org.jackhuang.hmcl.event.EventBus import org.jackhuang.hmcl.event.RefreshedVersionsEvent +import org.jackhuang.hmcl.game.GameVersion.minecraftVersion import org.jackhuang.hmcl.game.LauncherHelper -import org.jackhuang.hmcl.game.minecraftVersion import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.ui.construct.RipplerContainer import org.jackhuang.hmcl.ui.download.DownloadWizardProvider import org.jackhuang.hmcl.ui.wizard.DecoratorPage +import org.jackhuang.hmcl.util.channel import org.jackhuang.hmcl.util.onChange +import org.jackhuang.hmcl.util.plusAssign /** * @see /assets/fxml/main.fxml @@ -59,9 +61,9 @@ class MainPage : StackPane(), DecoratorPage { btnLaunch.limitWidth(40.0) btnLaunch.limitHeight(40.0) - EVENT_BUS.channel() += { -> loadVersions() } - EVENT_BUS.channel() += this::onProfilesLoading - EVENT_BUS.channel() += this::onProfileChanged + EventBus.EVENT_BUS.channel() += { -> loadVersions() } + EventBus.EVENT_BUS.channel() += this::onProfilesLoading + EventBus.EVENT_BUS.channel() += this::onProfileChanged btnAdd.setOnMouseClicked { Controllers.decorator.startWizard(DownloadWizardProvider(), "Install New Game") } btnRefresh.setOnMouseClicked { Settings.selectedProfile.repository.refreshVersions() } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt index 2b64bde5c..a7b07e976 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt @@ -27,9 +27,10 @@ import javafx.stage.FileChooser import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.mod.ModManager import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.task.task +import org.jackhuang.hmcl.task.Schedulers import org.jackhuang.hmcl.util.onChange import org.jackhuang.hmcl.util.onChangeAndOperateWeakly +import org.jackhuang.hmcl.util.task import java.util.* class ModController { @@ -77,7 +78,7 @@ class ModController { modManager.removeMods(versionId, modInfo) loadMods(modManager, versionId) }.apply { - modInfo.activeProperty.onChange { + modInfo.activeProperty().onChange { if (it) styleClass -= "disabled" else @@ -91,7 +92,7 @@ class ModController { runOnUiThread { rootPane.children += contentPane } it["list"] = list } - }.subscribe(Scheduler.JAVAFX) { variables -> + }.subscribe(Schedulers.javafx()) { variables -> parentTab.selectionModel.selectedItemProperty().onChangeAndOperateWeakly { if (it?.userData == this) { modPane.children.setAll(variables.get>("list")) @@ -106,6 +107,6 @@ class ModController { chooser.extensionFilters.setAll(FileChooser.ExtensionFilter("Mod", "*.jar", "*.zip", "*.litemod")) val res = chooser.showOpenDialog(Controllers.stage) ?: return task { modManager.addMod(versionId, res) } - .subscribe(task(Scheduler.JAVAFX) { loadMods(modManager, versionId) }) + .subscribe(task(Schedulers.javafx()) { loadMods(modManager, versionId) }) } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModItem.kt index f200fe2c5..a5780de2b 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModItem.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModItem.kt @@ -53,10 +53,10 @@ class ModItem(info: ModInfo, private val deleteCallback: (ModItem) -> Unit) : Bo style = "-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;" JFXDepthManager.setDepth(this, 1) lblModFileName.text = info.fileName - lblModAuthor.text = "${info.name}, Version: ${info.version}, Game Version: ${info.mcversion}, Authors: ${info.authors}" + lblModAuthor.text = "${info.name}, Version: ${info.version}, Game Version: ${info.gameVersion}, Authors: ${info.authors}" chkEnabled.isSelected = info.isActive chkEnabled.selectedProperty().onChange { - info.activeProperty.set(it) + info.activeProperty().set(it) } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt index e9365a230..0b5683d40 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt @@ -24,25 +24,21 @@ import javafx.scene.Node import javafx.scene.control.Label import javafx.scene.control.ScrollPane import javafx.scene.control.Toggle -import javafx.scene.control.ToggleGroup import javafx.scene.image.Image import javafx.scene.image.ImageView -import javafx.scene.layout.BorderPane -import javafx.scene.layout.Pane import javafx.scene.layout.VBox -import javafx.stage.DirectoryChooser import javafx.stage.FileChooser import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.EnumGameDirectory import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.VersionSetting -import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.task.task +import org.jackhuang.hmcl.task.Schedulers import org.jackhuang.hmcl.ui.construct.ComponentList import org.jackhuang.hmcl.ui.construct.MultiFileItem import org.jackhuang.hmcl.ui.construct.NumberValidator import org.jackhuang.hmcl.util.JavaVersion -import org.jackhuang.hmcl.util.OS +import org.jackhuang.hmcl.util.OperatingSystem +import org.jackhuang.hmcl.util.task class VersionSettingsController { var lastVersionSetting: VersionSetting? = null @@ -74,7 +70,7 @@ class VersionSettingsController { lateinit var versionId: String fun initialize() { - lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OS.TOTAL_MEMORY}MB" + lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OperatingSystem.TOTAL_MEMORY}MB" scroll.smoothScrolling() @@ -102,9 +98,9 @@ class VersionSettingsController { task { it["list"] = JavaVersion.getJREs().values.map { javaVersion -> - javaItem.createChildren(javaVersion.longVersion, javaVersion.binary.absolutePath, javaVersion) + javaItem.createChildren(javaVersion.version, javaVersion.binary.absolutePath, javaVersion) } - }.subscribe(Scheduler.JAVAFX) { + }.subscribe(Schedulers.javafx()) { javaItem.loadChildren(it.get>("list")) } @@ -175,7 +171,7 @@ class VersionSettingsController { if (newValue == javaItem.radioCustom) { // Custom version.java = "Custom" } else { - version.java = ((newValue as JFXRadioButton).userData as JavaVersion).longVersion + version.java = ((newValue as JFXRadioButton).userData as JavaVersion).version } } javaItem.group.properties[javaGroupKey] = listener @@ -220,7 +216,7 @@ class VersionSettingsController { private fun initJavaSubtitle(version: VersionSetting) { task { it["java"] = version.javaVersion } - .subscribe(task(Scheduler.JAVAFX) { javaItem.subtitle = it.get("java")?.binary?.absolutePath ?: "Invalid Java Directory" }) + .subscribe(task(Schedulers.javafx()) { javaItem.subtitle = it.get("java")?.binary?.absolutePath ?: "Invalid Java Directory" }) } private fun initGameDirSubtitle(version: VersionSetting) { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.kt index 8a6f16794..9515c8bf4 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.kt @@ -26,10 +26,11 @@ import javafx.scene.layout.StackPane import org.jackhuang.hmcl.auth.AuthInfo import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.Settings -import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.task.taskResult +import org.jackhuang.hmcl.task.Schedulers +import org.jackhuang.hmcl.util.taskResult class YggdrasilAccountLoginPane(private val oldAccount: YggdrasilAccount, private val success: (AuthInfo) -> Unit, private val failed: () -> Unit) : StackPane() { @FXML lateinit var lblUsername: Label @@ -54,12 +55,12 @@ class YggdrasilAccountLoginPane(private val oldAccount: YggdrasilAccount, privat lblCreationWarning.text = "" taskResult("login") { try { - val account = YggdrasilAccount.fromUsername(username, password) + val account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password) account.logIn(Settings.proxy) } catch (e: Exception) { e } - }.subscribe(Scheduler.JAVAFX) { + }.subscribe(Schedulers.javafx()) { val account: Any = it["login"] if (account is AuthInfo) { success(account) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt index 3a541aa0a..eca1c04ca 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.download import javafx.scene.Node -import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider import org.jackhuang.hmcl.game.HMCLModpackInstallTask import org.jackhuang.hmcl.game.HMCLModpackManifest import org.jackhuang.hmcl.game.MMCInstallVersionSettingTask @@ -27,9 +26,9 @@ import org.jackhuang.hmcl.setting.EnumGameDirectory import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.task import org.jackhuang.hmcl.ui.wizard.WizardController import org.jackhuang.hmcl.ui.wizard.WizardProvider +import org.jackhuang.hmcl.util.task import java.io.File class DownloadWizardProvider(): WizardProvider() { @@ -79,11 +78,11 @@ class DownloadWizardProvider(): WizardProvider() { } return when (modpack.manifest) { - is CurseForgeModpackManifest -> CurseForgeModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as CurseForgeModpackManifest, name) + is CurseManifest -> CurseInstallTask(profile.dependency, selectedFile, modpack.manifest as CurseManifest, name) is HMCLModpackManifest -> HMCLModpackInstallTask(profile, selectedFile, modpack, name) - is InstanceConfiguration -> MMCModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as InstanceConfiguration, name) with MMCInstallVersionSettingTask(profile, modpack.manifest as InstanceConfiguration, name) + is MultiMCInstanceConfiguration -> MultiMCModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as MultiMCInstanceConfiguration, name).with(MMCInstallVersionSettingTask(profile, modpack.manifest as MultiMCInstanceConfiguration, name)) else -> throw Error() - } with finalizeTask + }.with(finalizeTask) } override fun finish(settings: MutableMap): Any? { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/InstallWizardProvider.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/InstallWizardProvider.kt index 994af127e..8b2517427 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/InstallWizardProvider.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/InstallWizardProvider.kt @@ -2,20 +2,12 @@ package org.jackhuang.hmcl.ui.download import javafx.scene.Node import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider -import org.jackhuang.hmcl.game.HMCLModpackInstallTask -import org.jackhuang.hmcl.game.HMCLModpackManifest import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.mod.CurseForgeModpackInstallTask -import org.jackhuang.hmcl.mod.CurseForgeModpackManifest import org.jackhuang.hmcl.mod.Modpack -import org.jackhuang.hmcl.setting.EnumGameDirectory import org.jackhuang.hmcl.setting.Profile -import org.jackhuang.hmcl.setting.Settings -import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.task import org.jackhuang.hmcl.ui.wizard.WizardController import org.jackhuang.hmcl.ui.wizard.WizardProvider +import org.jackhuang.hmcl.util.task import java.io.File class InstallWizardProvider(val profile: Profile, val gameVersion: String, val version: Version, val forge: String? = null, val liteloader: String? = null, val optifine: String? = null): WizardProvider() { @@ -27,20 +19,20 @@ class InstallWizardProvider(val profile: Profile, val gameVersion: String, val v var ret = task {} if (settings.containsKey("forge")) - ret = ret with profile.dependency.installLibraryAsync(gameVersion, version, "forge", settings["forge"] as String) + ret = ret.with(profile.dependency.installLibraryAsync(gameVersion, version, "forge", settings["forge"] as String)) if (settings.containsKey("liteloader")) - ret = ret with profile.dependency.installLibraryAsync(gameVersion, version, "liteloader", settings["liteloader"] as String) + ret = ret.with(profile.dependency.installLibraryAsync(gameVersion, version, "liteloader", settings["liteloader"] as String)) if (settings.containsKey("optifine")) - ret = ret with profile.dependency.installLibraryAsync(gameVersion, version, "optifine", settings["optifine"] as String) + ret = ret.with(profile.dependency.installLibraryAsync(gameVersion, version, "optifine", settings["optifine"] as String)) - return ret with task { profile.repository.refreshVersions() } + return ret.with(task { profile.repository.refreshVersions() }) } override fun createPage(controller: WizardController, step: Int, settings: MutableMap): Node { return when (step) { - 0 -> AdditionalInstallersPage(this, controller, profile.repository, BMCLAPIDownloadProvider) + 0 -> AdditionalInstallersPage(this, controller, profile.repository, BMCLAPIDownloadProvider.INSTANCE) else -> throw IllegalStateException() } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/VersionsPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/VersionsPage.kt index 7225a3739..4695398cc 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/VersionsPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/VersionsPage.kt @@ -24,6 +24,7 @@ import javafx.scene.layout.StackPane import org.jackhuang.hmcl.download.DownloadProvider import org.jackhuang.hmcl.download.RemoteVersion import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.Schedulers import org.jackhuang.hmcl.task.TaskExecutor import org.jackhuang.hmcl.ui.animation.ContainerAnimations import org.jackhuang.hmcl.ui.animation.TransitionHandler @@ -53,9 +54,9 @@ class VersionsPage(private val controller: WizardController, private val gameVer } override fun refresh() { - executor = versionList.refreshAsync(downloadProvider).subscribe(Scheduler.JAVAFX) { + executor = versionList.refreshAsync(downloadProvider).subscribe(Schedulers.javafx()) { val versions = ArrayList(versionList.getVersions(gameVersion)) - versions.sortWith(RemoteVersion) + versions.sortWith(RemoteVersion.RemoteVersionComparator.INSTANCE) for (version in versions) { list.items.add(VersionsPageItem(version)) } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/export/ExportWizardProvider.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/export/ExportWizardProvider.kt index 734d8a51c..ded404b36 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/export/ExportWizardProvider.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/export/ExportWizardProvider.kt @@ -34,10 +34,12 @@ class ExportWizardProvider(private val profile: Profile, private val version: St @Suppress("UNCHECKED_CAST") return HMCLModpackExportTask(profile.repository, version, settings[ModpackFileSelectionPage.MODPACK_FILE_SELECTION] as List, Modpack( - name = settings[ModpackInfoPage.MODPACK_NAME] as String, - author = settings[ModpackInfoPage.MODPACK_AUTHOR] as String, - version = settings[ModpackInfoPage.MODPACK_VERSION] as String, - description = settings[ModpackInfoPage.MODPACK_DESCRIPTION] as String + settings[ModpackInfoPage.MODPACK_NAME] as String, + settings[ModpackInfoPage.MODPACK_AUTHOR] as String, + settings[ModpackInfoPage.MODPACK_VERSION] as String, + null, + settings[ModpackInfoPage.MODPACK_DESCRIPTION] as String, + null ), settings[ModpackInfoPage.MODPACK_FILE] as File) } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.kt index 79731c39f..7a2cce3e3 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.kt @@ -23,10 +23,7 @@ import javafx.application.Platform import javafx.scene.control.Label import javafx.scene.layout.StackPane import javafx.scene.layout.VBox -import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.TaskExecutor -import org.jackhuang.hmcl.task.TaskListener +import org.jackhuang.hmcl.task.* import java.util.* import kotlin.concurrent.thread @@ -94,28 +91,28 @@ interface AbstractWizardDisplayer : WizardDisplayer { navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH) - task.with(org.jackhuang.hmcl.task.task(Scheduler.JAVAFX) { + task.with(org.jackhuang.hmcl.util.task(Schedulers.javafx()) { navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH) }).executor().apply { @Suppress("NAME_SHADOWING") - taskListener = object : TaskListener { + taskListener = object : TaskListener() { override fun onReady(task: Task) { - Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get()) } + Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / runningTasks) } } override fun onFinished(task: Task) { Platform.runLater { - label.text = task.title + label.text = task.name ++finishedTasks - tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get()) + tasksBar.progressProperty().set(finishedTasks * 1.0 / runningTasks) } } override fun onFailed(task: Task, throwable: Throwable) { Platform.runLater { - label.text = task.title + label.text = task.name ++finishedTasks - tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get()) + tasksBar.progressProperty().set(finishedTasks * 1.0 / runningTasks) } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/AppDataUpgrader.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/AppDataUpgrader.kt index a3ea4179f..2df501053 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/AppDataUpgrader.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/AppDataUpgrader.kt @@ -37,10 +37,12 @@ import java.util.jar.Pack200 import java.util.jar.JarOutputStream import org.jackhuang.hmcl.util.* import java.net.URISyntaxException -import org.jackhuang.hmcl.util.OS import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.ui.alert +import org.jackhuang.hmcl.util.Constants.GSON +import org.jackhuang.hmcl.util.Logging.LOG import org.jackhuang.hmcl.util.VersionNumber +import java.net.Proxy import java.net.URI class AppDataUpgrader : IUpgrader { @@ -123,18 +125,18 @@ class AppDataUpgrader : IUpgrader { else { var url = URL_PUBLISH if (map != null) - if (map.containsKey(OS.CURRENT_OS.checkedName)) - url = map.get(OS.CURRENT_OS.checkedName)!! - else if (map.containsKey(OS.UNKNOWN.checkedName)) - url = map.get(OS.UNKNOWN.checkedName)!! + if (map.containsKey(OperatingSystem.CURRENT_OS.checkedName)) + url = map.get(OperatingSystem.CURRENT_OS.checkedName)!! + else if (map.containsKey(OperatingSystem.UNKNOWN.checkedName)) + url = map.get(OperatingSystem.UNKNOWN.checkedName)!! try { java.awt.Desktop.getDesktop().browse(URI(url)) } catch (e: URISyntaxException) { LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e) - OS.setClipboard(url) + OperatingSystem.setClipboard(url) } catch (e: IOException) { LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e) - OS.setClipboard(url) + OperatingSystem.setClipboard(url) } } @@ -144,16 +146,17 @@ class AppDataUpgrader : IUpgrader { class AppDataUpgraderPackGzTask(downloadLink: String, private val newestVersion: String, private val expectedHash: String) : Task() { private val tempFile: File = File.createTempFile("hmcl", ".pack.gz") - override val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, expectedHash)) + private val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, Proxy.NO_PROXY, expectedHash)) + override fun getDependents() = dependents init { - onDone += { event -> if (event.failed) tempFile.delete() } + onDone() += { event -> if (event.isFailed) tempFile.delete() } } override fun execute() { val json = HashMap() var f = getSelf(newestVersion) - if (!f.parentFile.makeDirectory()) + if (!FileUtils.makeDirectory(f.parentFile)) throw IOException("Failed to make directories: " + f.parent) var i = 0 @@ -187,15 +190,15 @@ class AppDataUpgrader : IUpgrader { } class AppDataUpgraderJarTask(downloadLink: String, private val newestVersion: String, expectedHash: String) : Task() { - override var title = "Upgrade" - set(value) {} private val tempFile = File.createTempFile("hmcl", ".jar") init { - onDone += { event -> if (event.failed) tempFile.delete() } + name = "Upgrade" + onDone() += { event -> if (event.isFailed) tempFile.delete() } } - override val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, expectedHash)) + private val dependents = listOf(FileDownloadTask(downloadLink.toURL(), tempFile, Proxy.NO_PROXY, expectedHash)) + override fun getDependents() = dependents override fun execute() { val json = HashMap() diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/UpdateChecker.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/UpdateChecker.kt index 79481d39e..961bd1092 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/UpdateChecker.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/upgrade/UpdateChecker.kt @@ -21,6 +21,8 @@ import java.io.IOException import com.google.gson.JsonSyntaxException import org.jackhuang.hmcl.task.TaskResult import org.jackhuang.hmcl.util.* +import org.jackhuang.hmcl.util.Constants.GSON +import org.jackhuang.hmcl.util.Logging.LOG import java.util.logging.Level @@ -58,10 +60,10 @@ class UpdateChecker(var base: VersionNumber, var type: String) { */ fun process(showMessage: Boolean): TaskResult { return object : TaskResult() { - override val id = "update_checker.process" + override fun getId() = "update_checker.process" override fun execute() { if (newVersion == null) { - versionString = ("http://huangyuhui.duapp.com/info.php?type=$type").toURL().doGet() + versionString = NetworkUtils.doGet("http://huangyuhui.duapp.com/info.php?type=$type".toURL()) newVersion = VersionNumber.asVersion(versionString!!) } @@ -83,12 +85,12 @@ class UpdateChecker(var base: VersionNumber, var type: String) { @Synchronized fun requestDownloadLink(): TaskResult> { return object : TaskResult>() { - override val id = "update_checker.request_download_link" + override fun getId() = "update_checker.request_download_link" override fun execute() { @Suppress("UNCHECKED_CAST") if (download_link == null) try { - download_link = GSON.fromJson(("http://huangyuhui.duapp.com/update_link.php?type=$type").toURL().doGet(), Map::class.java) as Map + download_link = GSON.fromJson(NetworkUtils.doGet("http://huangyuhui.duapp.com/update_link.php?type=$type".toURL()), Map::class.java) as Map } catch (e: JsonSyntaxException) { LOG.log(Level.WARNING, "Failed to get update link.", e) } catch (e: IOException) { diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt similarity index 54% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt rename to HMCL/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt index 84c3cfa0b..e9cc0a01d 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt @@ -17,10 +17,25 @@ */ package org.jackhuang.hmcl.util +import com.google.gson.Gson +import com.google.gson.JsonParseException +import com.google.gson.reflect.TypeToken import javafx.beans.property.Property +import javafx.event.Event.fireEvent +import org.jackhuang.hmcl.event.EventBus +import org.jackhuang.hmcl.event.EventManager +import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.Schedulers +import org.jackhuang.hmcl.task.Task +import org.jackhuang.hmcl.task.TaskResult +import org.jackhuang.hmcl.util.Constants.UI_THREAD_SCHEDULER +import java.io.InputStream +import java.lang.reflect.Type +import java.net.URL +import java.rmi.activation.Activatable.unregister import java.util.* +import java.util.concurrent.Callable import java.util.concurrent.atomic.AtomicReference -import kotlin.collections.HashMap inline fun ignoreException(func: () -> Unit) { try { @@ -34,28 +49,6 @@ inline fun ignoreThrowable(func: () -> Unit) { } catch (ignore: Throwable) {} } -fun unmodifiableMap(map: Map?): Map? = - if (map == null) null - else Collections.unmodifiableMap(map) - -fun copyMap(map: Map?): MutableMap? = - if (map == null) null - else HashMap(map) - -fun unmodifiableList(list: List?): List? = - if (list == null) null - else Collections.unmodifiableList(list) - -fun copyList(list: List?): MutableList? = - if (list == null) null - else LinkedList(list) - -fun merge(vararg c: Collection?): List = LinkedList().apply { - for (a in c) - if (a != null) - addAll(a) -} - fun isBlank(str: String?) = str?.isBlank() ?: true fun isNotBlank(str: String?) = !isBlank(str) @@ -84,42 +77,7 @@ fun String.asVersion(): String? { fun Any?.toStringOrEmpty() = this?.toString().orEmpty() -fun parseParams(addBefore: String, objects: Collection<*>, addAfter: String): String { - return parseParams(addBefore, objects.toTypedArray(), addAfter) -} - -fun parseParams(addBefore: String, objects: Array<*>, addAfter: String): String { - return parseParams({ addBefore }, objects, { addAfter }) -} - -fun parseParams(beforeFunc: (Any?) -> String, params: Array<*>?, afterFunc: (Any?) -> String): String { - if (params == null) - return "" - val sb = StringBuilder() - for (i in params.indices) { - val param = params[i] - val addBefore = beforeFunc(param) - val addAfter = afterFunc(param) - if (i > 0) - sb.append(addAfter).append(addBefore) - if (param == null) - sb.append("null") - else if (param.javaClass.isArray) { - sb.append("[") - if (param is Array<*>) { - sb.append(parseParams(beforeFunc, param, afterFunc)) - } else - for (j in 0..java.lang.reflect.Array.getLength(param) - 1) { - if (j > 0) - sb.append(addAfter) - sb.append(addBefore).append(java.lang.reflect.Array.get(param, j)) - } - sb.append("]") - } else - sb.append(addBefore).append(params[i]) - } - return sb.toString() -} +fun String.toURL() = URL(this) fun Collection.containsOne(vararg matcher: String): Boolean { for (a in this) @@ -131,9 +89,34 @@ fun Collection.containsOne(vararg matcher: String): Boolean { fun Property.updateAsync(newValue: T, update: AtomicReference) { if (update.getAndSet(newValue) == null) { - UI_THREAD_SCHEDULER { + UI_THREAD_SCHEDULER.accept(Runnable { val current = update.getAndSet(null) this.value = current - } + }) } -} \ No newline at end of file +} + +inline fun typeOf(): Type = object : TypeToken() {}.type + +inline fun Gson.fromJson(json: String): T? = fromJson(json, T::class.java) + +inline fun Gson.fromJsonQuietly(json: String): T? { + try { + return fromJson(json) + } catch (json: JsonParseException) { + return null + } +} + +fun task(scheduler: Scheduler = Schedulers.defaultScheduler(), closure: (AutoTypingMap) -> Unit): Task = Task.of(closure, scheduler) +fun taskResult(id: String, callable: Callable): TaskResult = Task.ofResult(id, callable) +fun taskResult(id: String, callable: (AutoTypingMap) -> V): TaskResult = Task.ofResult(id, callable) + +fun InputStream.readFullyAsString() = IOUtils.readFullyAsString(this) +inline fun EventBus.channel() = channel(T::class.java) + +operator fun EventManager.plusAssign(func: (T) -> Unit) = register(func) +operator fun EventManager.plusAssign(func: () -> Unit) = register(func) +operator fun EventManager.minusAssign(func: (T) -> Unit) = unregister(func) +operator fun EventManager.minusAssign(func: () -> Unit) = unregister(func) +operator fun EventManager.invoke(event: T) = fireEvent(event) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Lib.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/util/Lib.kt similarity index 100% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Lib.kt rename to HMCL/src/main/kotlin/org/jackhuang/hmcl/util/Lib.kt diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Properties.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/util/Properties.kt similarity index 100% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Properties.kt rename to HMCL/src/main/kotlin/org/jackhuang/hmcl/util/Properties.kt diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ReflectionHelper.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/util/ReflectionHelper.kt similarity index 100% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ReflectionHelper.kt rename to HMCL/src/main/kotlin/org/jackhuang/hmcl/util/ReflectionHelper.kt diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java new file mode 100644 index 000000000..dfff76dc5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java @@ -0,0 +1,40 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth; + +import java.net.Proxy; +import java.util.Map; + +/** + * + * @author huangyuhui + */ +public abstract class Account { + + public abstract String getUsername(); + + public AuthInfo logIn() throws AuthenticationException { + return logIn(Proxy.NO_PROXY); + } + + public abstract AuthInfo logIn(Proxy proxy) throws AuthenticationException; + + public abstract void logOut(); + + public abstract Map toStorage(); +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/ParallelTask.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java similarity index 67% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/ParallelTask.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java index ac4f4a05e..4babfeff4 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/ParallelTask.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java @@ -15,17 +15,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.task +package org.jackhuang.hmcl.auth; + +import java.util.Map; /** - * The tasks that provides a way to execute tasks parallelly. - * Fails when some of [tasks] failed. * - * @param tasks the tasks that can be executed parallelly. + * @author huangyuhui */ -class ParallelTask(vararg tasks: Task): Task() { - override val hidden: Boolean = true - override val dependents: Collection = listOf(*tasks) +public abstract class AccountFactory { - override fun execute() {} -} \ No newline at end of file + public final T fromUsername(String username) { + return fromUsername(username, ""); + } + + public abstract T fromUsername(String username, String password); + + public abstract T fromStorage(Map storage); +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java new file mode 100644 index 000000000..4e0488df0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java @@ -0,0 +1,96 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth; + +import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; +import org.jackhuang.hmcl.util.UUIDTypeAdapter; + +/** + * + * @author huangyuhui + */ +public final class AuthInfo { + + private final String username; + private final String userId; + private final String authToken; + private final UserType userType; + private final String userProperties; + private final String userPropertyMap; + + public AuthInfo(String username, String userId, String authToken) { + this(username, userId, authToken, UserType.LEGACY); + } + + public AuthInfo(String username, String userId, String authToken, UserType userType) { + this(username, userId, authToken, userType, "{}"); + } + + public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties) { + this(username, userId, authToken, userType, userProperties, "{}"); + } + + public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap) { + this.username = username; + this.userId = userId; + this.authToken = authToken; + this.userType = userType; + this.userProperties = userProperties; + this.userPropertyMap = userPropertyMap; + } + + public AuthInfo(GameProfile profile, String authToken, UserType userType, String userProperties) { + this(profile.getName(), UUIDTypeAdapter.fromUUID(profile.getId()), authToken, userType, userProperties); + } + + public String getUsername() { + return username; + } + + public String getUserId() { + return userId; + } + + public String getAuthToken() { + return authToken; + } + + public UserType getUserType() { + return userType; + } + + /** + * Properties of this user. + * Don't know the difference between user properties and user property map. + * + * @return the user property map in JSON. + */ + public String getUserProperties() { + return userProperties; + } + + /** + * Properties of this user. + * Don't know the difference between user properties and user property map. + * + * @return the user property map in JSON. + */ + public String getUserPropertyMap() { + return userPropertyMap; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthenticationException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthenticationException.java new file mode 100644 index 000000000..d4771358c --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthenticationException.java @@ -0,0 +1,37 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth; + +/** + * + * @author huangyuhui + */ +public class AuthenticationException extends Exception { + + public AuthenticationException() { + super(); + } + + public AuthenticationException(String message) { + super(message); + } + + public AuthenticationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java new file mode 100644 index 000000000..97899dd09 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java @@ -0,0 +1,81 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth; + +import java.net.Proxy; +import java.util.Map; +import java.util.Objects; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.StringUtils; + +/** + * + * @author huang + */ +public class OfflineAccount extends Account { + + private final String username; + private final String uuid; + + OfflineAccount(String username, String uuid) { + Objects.requireNonNull(username); + Objects.requireNonNull(uuid); + + this.username = username; + this.uuid = uuid; + + if (StringUtils.isBlank(username)) + throw new IllegalArgumentException("Username cannot be blank"); + } + + public String getUuid() { + return uuid; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public AuthInfo logIn(Proxy proxy) throws AuthenticationException { + if (StringUtils.isBlank(username) || StringUtils.isBlank(uuid)) + throw new AuthenticationException("Username cannot be empty"); + + return new AuthInfo(username, uuid, uuid); + } + + @Override + public void logOut() { + // Offline account need not log out. + } + + @Override + public Map toStorage() { + return Lang.mapOf( + new Pair<>("uuid", uuid), + new Pair<>("username", username) + ); + } + + @Override + public String toString() { + return "OfflineAccount[username=" + username + ", uuid=" + uuid + "]"; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java new file mode 100644 index 000000000..e74900359 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java @@ -0,0 +1,55 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth; + +import java.util.Map; +import org.jackhuang.hmcl.util.DigestUtils; + +/** + * + * @author huangyuhui + */ +public class OfflineAccountFactory extends AccountFactory { + public static final OfflineAccountFactory INSTANCE = new OfflineAccountFactory(); + + private OfflineAccountFactory() { + } + + @Override + public OfflineAccount fromUsername(String username, String password) { + return new OfflineAccount(username, getUUIDFromUserName(username)); + } + + @Override + public OfflineAccount fromStorage(Map storage) { + Object username = storage.get("username"); + if (username == null || !(username instanceof String)) + throw new IllegalStateException("Offline account configuration malformed."); + + Object uuid = storage.get("uuid"); + if (uuid == null || !(uuid instanceof String)) + uuid = getUUIDFromUserName((String) username); + + return new OfflineAccount((String) username, (String) uuid); + } + + private static String getUUIDFromUserName(String username) { + return DigestUtils.md5Hex(username); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/UserType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/UserType.java new file mode 100644 index 000000000..6d99686e0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/UserType.java @@ -0,0 +1,49 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author huangyuhui + */ +public enum UserType { + LEGACY, + MOJANG; + + public static UserType fromName(String name) { + return BY_NAME.get(name.toLowerCase()); + } + + public static UserType fromLegacy(boolean isLegacy) { + return isLegacy ? LEGACY : MOJANG; + } + + static { + HashMap byName = new HashMap<>(); + for (UserType type : values()) + byName.put(type.name().toLowerCase(), type); + BY_NAME = Collections.unmodifiableMap(byName); + } + + public static final Map BY_NAME; + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.java new file mode 100644 index 000000000..2d5ee7b00 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.java @@ -0,0 +1,61 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.jackhuang.hmcl.auth.yggdrasil; + +import java.util.Map; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Pair; + +/** + * + * @author huangyuhui + */ +public final class AuthenticationRequest { + + /** + * The user name of Minecraft account. + */ + private final String username; + + /** + * The password of Minecraft account. + */ + private final String password; + + /** + * The client token of this game. + */ + private final String clientToken; + + private final Map agent = Lang.mapOf( + new Pair("name", "minecraft"), + new Pair("version", 1)); + + private final boolean requestUser = true; + + public AuthenticationRequest(String username, String password, String clientToken) { + this.username = username; + this.password = password; + this.clientToken = clientToken; + } + + public String getUsername() { + return username; + } + + public String getClientToken() { + return clientToken; + } + + public Map getAgent() { + return agent; + } + + public boolean isRequestUser() { + return requestUser; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.java new file mode 100644 index 000000000..977e8500e --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.java @@ -0,0 +1,102 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth.yggdrasil; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; +import java.util.UUID; + +/** + * + * @author huang + */ +public final class GameProfile { + + private final UUID id; + private final String name; + private final PropertyMap properties; + private final boolean legacy; + + public GameProfile() { + this(null, null); + } + + public GameProfile(UUID id, String name) { + this(id, name, new PropertyMap(), false); + } + + public GameProfile(UUID id, String name, PropertyMap properties, boolean legacy) { + this.id = id; + this.name = name; + this.properties = properties; + this.legacy = legacy; + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public PropertyMap getProperties() { + return properties; + } + + public boolean isLegacy() { + return legacy; + } + + public static class Serializer implements JsonSerializer, JsonDeserializer { + + public static final Serializer INSTANCE = new Serializer(); + + private Serializer() { + } + + @Override + public JsonElement serialize(GameProfile src, Type type, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + if (src.getId() != null) + result.add("id", context.serialize(src.getId())); + if (src.getName() != null) + result.addProperty("name", src.getName()); + return result; + } + + @Override + public GameProfile deserialize(JsonElement je, Type type, JsonDeserializationContext context) throws JsonParseException { + if (!(je instanceof JsonObject)) + throw new JsonParseException("The json element is not a JsonObject."); + + JsonObject json = (JsonObject) je; + + UUID id = json.has("id") ? context.deserialize(json.get("id"), UUID.class) : null; + String name = json.has("name") ? json.getAsJsonPrimitive("name").getAsString() : null; + return new GameProfile(id, name); + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidCredentialsException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidCredentialsException.java new file mode 100644 index 000000000..6cbbb4da0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidCredentialsException.java @@ -0,0 +1,37 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth.yggdrasil; + +import org.jackhuang.hmcl.auth.AuthenticationException; + +/** + * + * @author huangyuhui + */ +public final class InvalidCredentialsException extends AuthenticationException { + + private final YggdrasilAccount account; + + public InvalidCredentialsException(YggdrasilAccount account) { + this.account = account; + } + + public YggdrasilAccount getAccount() { + return account; + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/UserType.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidTokenException.java similarity index 64% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/UserType.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidTokenException.java index ea7d72764..f452b88b6 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/UserType.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidTokenException.java @@ -15,20 +15,24 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.auth +package org.jackhuang.hmcl.auth.yggdrasil; -enum class UserType() { - LEGACY, - MOJANG; +import org.jackhuang.hmcl.auth.AuthenticationException; - companion object { +/** + * + * @author huangyuhui + */ +public class InvalidTokenException extends AuthenticationException { - fun fromName(name: String) = BY_NAME[name.toLowerCase()] - fun fromLegacy(isLegacy: Boolean) = if (isLegacy) LEGACY else MOJANG + private YggdrasilAccount account; - private val BY_NAME = HashMap().apply { - for (type in values()) - this[type.name.toLowerCase()] = type - } + public InvalidTokenException(YggdrasilAccount account) { + super(); + this.account = account; } -} \ No newline at end of file + + public YggdrasilAccount getAccount() { + return account; + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SimpleTask.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Property.java similarity index 67% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SimpleTask.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Property.java index b55a8db5c..2110bd88e 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SimpleTask.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Property.java @@ -1,7 +1,7 @@ /* * Hello Minecraft! Launcher. * Copyright (C) 2017 huangyuhui - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -15,12 +15,23 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.task +package org.jackhuang.hmcl.auth.yggdrasil; -import org.jackhuang.hmcl.util.AutoTypingMap +public class Property { -internal class SimpleTask @JvmOverloads constructor(private val runnable: (AutoTypingMap) -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() { - override fun execute() { - runnable(variables!!) + private final String name; + private final String value; + + public Property(String name, String value) { + this.name = name; + this.value = value; } -} \ No newline at end of file + + public String getName() { + return name; + } + + public String getValue() { + return value; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/PropertyMap.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/PropertyMap.java new file mode 100644 index 000000000..fdc6705dd --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/PropertyMap.java @@ -0,0 +1,116 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth.yggdrasil; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jackhuang.hmcl.util.Lang; + +public final class PropertyMap extends HashMap { + + public List> toList() { + List> properties = new ArrayList<>(); + for (Property profileProperty : values()) { + Map property = new HashMap<>(); + property.put("name", profileProperty.getName()); + property.put("value", profileProperty.getValue()); + properties.add(property); + } + return properties; + } + + public void fromList(List list) { + for (Object propertyMap : list) { + if (!(propertyMap instanceof Map)) + continue; + Optional name = Lang.get((Map) propertyMap, "name", String.class); + Optional value = Lang.get((Map) propertyMap, "value", String.class); + if (name.isPresent() && value.isPresent()) + put(name.get(), new Property(name.get(), value.get())); + } + } + + public static class Serializer implements JsonSerializer, JsonDeserializer { + + public static final Serializer INSTANCE = new Serializer(); + + private Serializer() { + } + + @Override + public PropertyMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + PropertyMap result = new PropertyMap(); + if (json instanceof JsonObject) { + for (Map.Entry entry : ((JsonObject) json).entrySet()) + if (entry.getValue() instanceof JsonArray) + for (JsonElement element : (JsonArray) entry.getValue()) + result.put(entry.getKey(), new Property(entry.getKey(), element.getAsString())); + } else if ((json instanceof JsonArray)) + for (JsonElement element : (JsonArray) json) + if ((element instanceof JsonObject)) { + JsonObject object = (JsonObject) element; + String name = object.getAsJsonPrimitive("name").getAsString(); + String value = object.getAsJsonPrimitive("value").getAsString(); + result.put(name, new Property(name, value)); + } + + return result; + } + + @Override + public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) { + JsonArray result = new JsonArray(); + for (Property property : src.values()) { + JsonObject object = new JsonObject(); + object.addProperty("name", property.getName()); + object.addProperty("value", property.getValue()); + result.add(object); + } + + return result; + } + } + + public static class LegacySerializer + implements JsonSerializer { + + @Override + public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + for (PropertyMap.Entry entry : src.entrySet()) { + JsonArray values = new JsonArray(); + values.add(new JsonPrimitive(entry.getValue().getValue())); + result.add(entry.getKey(), values); + } + return result; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/RefreshRequest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/RefreshRequest.java new file mode 100644 index 000000000..6e39b3da6 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/RefreshRequest.java @@ -0,0 +1,62 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth.yggdrasil; + +/** + * + * @author huang + */ +public final class RefreshRequest { + + private final String accessToken; + private final String clientToken; + private final GameProfile selectedProfile; + private final boolean requestUser; + + public RefreshRequest(String accessToken, String clientToken) { + this(accessToken, clientToken, null); + } + + public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile) { + this(accessToken, clientToken, selectedProfile, true); + } + + public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile, boolean requestUser) { + this.accessToken = accessToken; + this.clientToken = clientToken; + this.selectedProfile = selectedProfile; + this.requestUser = requestUser; + } + + public String getAccessToken() { + return accessToken; + } + + public String getClientToken() { + return clientToken; + } + + public GameProfile getSelectedProfile() { + return selectedProfile; + } + + public boolean isRequestUser() { + return requestUser; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Response.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Response.java new file mode 100644 index 000000000..88a850bf7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Response.java @@ -0,0 +1,82 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth.yggdrasil; + +/** + * + * @author huangyuhui + */ +public final class Response { + + private final String accessToken; + private final String clientToken; + private final GameProfile selectedProfile; + private final GameProfile[] availableProfiles; + private final User user; + private final String error; + private final String errorMessage; + private final String cause; + + public Response() { + this(null, null, null, null, null, null, null, null); + } + + public Response(String accessToken, String clientToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user, String error, String errorMessage, String cause) { + this.accessToken = accessToken; + this.clientToken = clientToken; + this.selectedProfile = selectedProfile; + this.availableProfiles = availableProfiles; + this.user = user; + this.error = error; + this.errorMessage = errorMessage; + this.cause = cause; + } + + public String getAccessToken() { + return accessToken; + } + + public String getClientToken() { + return clientToken; + } + + public GameProfile getSelectedProfile() { + return selectedProfile; + } + + public GameProfile[] getAvailableProfiles() { + return availableProfiles; + } + + public User getUser() { + return user; + } + + public String getError() { + return error; + } + + public String getErrorMessage() { + return errorMessage; + } + + public String getCause() { + return cause; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/User.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/User.java new file mode 100644 index 000000000..7f1be0172 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/User.java @@ -0,0 +1,56 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth.yggdrasil; + +import com.google.gson.JsonParseException; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huang + */ +public final class User implements Validation { + + private final String id; + private final PropertyMap properties; + + public User(String id) { + this(id, null); + } + + public User(String id, PropertyMap properties) { + this.id = id; + this.properties = properties; + } + + public String getId() { + return id; + } + + public PropertyMap getProperties() { + return properties; + } + + @Override + public void validate() throws JsonParseException { + if (StringUtils.isBlank(id)) + throw new JsonParseException("User id cannot be empty."); + } + +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskCallable.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ValidateRequest.java similarity index 61% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskCallable.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ValidateRequest.java index ceada420e..0d90bcbec 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskCallable.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ValidateRequest.java @@ -15,19 +15,28 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.task +package org.jackhuang.hmcl.auth.yggdrasil; -import org.jackhuang.hmcl.util.AutoTypingMap -import java.util.concurrent.Callable +/** + * + * @author huangyuhui + */ +public final class ValidateRequest { -internal class TaskCallable(override val id: String, private val callable: Callable) : TaskResult() { - override fun execute() { - result = callable.call() + private final String accessToken; + private final String clientToken; + + public ValidateRequest(String accessToken, String clientToken) { + this.accessToken = accessToken; + this.clientToken = clientToken; } + + public String getAccessToken() { + return accessToken; + } + + public String getClientToken() { + return clientToken; + } + } - -internal class TaskCallable2(override val id: String, private val callable: (AutoTypingMap) -> V) : TaskResult() { - override fun execute() { - result = callable(variables!!) - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java new file mode 100644 index 000000000..b054e7a35 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java @@ -0,0 +1,274 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth.yggdrasil; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import java.io.IOException; +import java.net.Proxy; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.AuthInfo; +import org.jackhuang.hmcl.auth.AuthenticationException; +import org.jackhuang.hmcl.auth.UserType; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.UUIDTypeAdapter; + +/** + * + * @author huang + */ +public final class YggdrasilAccount extends Account { + + private final String username; + private String password; + private String userId; + private String accessToken = null; + private String clientToken = randomToken(); + private boolean isOnline = false; + private PropertyMap userProperties = new PropertyMap(); + private GameProfile selectedProfile = null; + private GameProfile[] profiles; + private UserType userType = UserType.LEGACY; + + public YggdrasilAccount(String username) { + this.username = username; + } + + @Override + public String getUsername() { + return username; + } + + void setPassword(String password) { + this.password = password; + } + + public String getUserId() { + return userId; + } + + void setUserId(String userId) { + this.userId = userId; + } + + void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + String getClientToken() { + return clientToken; + } + + void setClientToken(String clientToken) { + this.clientToken = clientToken; + } + + PropertyMap getUserProperties() { + return userProperties; + } + + public GameProfile getSelectedProfile() { + return selectedProfile; + } + + void setSelectedProfile(GameProfile selectedProfile) { + this.selectedProfile = selectedProfile; + } + + public boolean isLoggedIn() { + return StringUtils.isNotBlank(accessToken); + } + + public boolean canPlayOnline() { + return isLoggedIn() && selectedProfile != null && isOnline; + } + + public boolean canLogIn() { + return !canPlayOnline() && StringUtils.isNotBlank(username) + && (StringUtils.isNotBlank(password) || StringUtils.isNotBlank(accessToken)); + } + + @Override + public AuthInfo logIn(Proxy proxy) throws AuthenticationException { + if (canPlayOnline()) + return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties)); + else { + logIn0(proxy); + if (!isLoggedIn()) + throw new AuthenticationException("Wrong password for account " + username); + + if (selectedProfile == null) + // TODO: multi-available-profiles support + throw new UnsupportedOperationException("Do not support multi-available-profiles account yet."); + else + return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties)); + } + } + + private void logIn0(Proxy proxy) throws AuthenticationException { + if (StringUtils.isNotBlank(accessToken)) { + if (StringUtils.isBlank(userId)) + if (StringUtils.isNotBlank(username)) + userId = username; + else + throw new AuthenticationException("Invalid uuid and username"); + if (checkTokenValidity(proxy)) { + isOnline = true; + return; + } + logIn1(ROUTE_REFRESH, new RefreshRequest(accessToken, clientToken), proxy); + } else if (StringUtils.isNotBlank(password)) + logIn1(ROUTE_AUTHENTICATE, new AuthenticationRequest(username, password, clientToken), proxy); + else + throw new AuthenticationException("Password cannot be blank"); + } + + private void logIn1(URL url, Object input, Proxy proxy) throws AuthenticationException { + Response response = makeRequest(url, input, proxy); + if (response == null || !clientToken.equals(response.getClientToken())) + throw new AuthenticationException("Client token changed"); + + if (response.getSelectedProfile() != null) + userType = UserType.fromLegacy(response.getSelectedProfile().isLegacy()); + else if (response.getAvailableProfiles() != null && response.getAvailableProfiles().length > 0) + userType = UserType.fromLegacy(response.getAvailableProfiles()[0].isLegacy()); + + User user = response.getUser(); + if (user == null || user.getId() == null) + userId = null; + else + userId = user.getId(); + + isOnline = true; + profiles = response.getAvailableProfiles(); + selectedProfile = response.getSelectedProfile(); + userProperties.clear(); + accessToken = response.getAccessToken(); + + if (user != null && user.getProperties() != null) + userProperties.putAll(user.getProperties()); + } + + @Override + public void logOut() { + password = null; + userId = null; + accessToken = null; + isOnline = false; + userProperties.clear(); + profiles = null; + selectedProfile = null; + } + + @Override + public Map toStorage() { + HashMap result = new HashMap<>(); + + result.put(STORAGE_KEY_USER_NAME, getUsername()); + result.put(STORAGE_KEY_CLIENT_TOKEN, getClientToken()); + if (getUserId() != null) + result.put(STORAGE_KEY_USER_ID, getUserId()); + if (!userProperties.isEmpty()) + result.put(STORAGE_KEY_USER_PROPERTIES, userProperties.toList()); + GameProfile profile = selectedProfile; + if (profile != null && profile.getName() != null && profile.getId() != null) { + result.put(STORAGE_KEY_PROFILE_NAME, profile.getName()); + result.put(STORAGE_KEY_PROFILE_ID, profile.getId()); + + if (!profile.getProperties().isEmpty()) + result.put(STORAGE_KEY_PROFILE_PROPERTIES, profile.getProperties().toList()); + } + + if (StringUtils.isNotBlank(accessToken)) + result.put(STORAGE_KEY_ACCESS_TOKEN, accessToken); + + return result; + } + + private Response makeRequest(URL url, Object input, Proxy proxy) throws AuthenticationException { + try { + String jsonResult = input == null ? NetworkUtils.doGet(url, proxy) : NetworkUtils.doPost(url, GSON.toJson(input), "application/json", proxy); + Response response = GSON.fromJson(jsonResult, Response.class); + if (response == null) + return null; + if (!StringUtils.isBlank(response.getError())) { + if (response.getErrorMessage() != null) + if (response.getErrorMessage().contains("Invalid credentials")) + throw new InvalidCredentialsException(this); + else if (response.getErrorMessage().contains("Invalid token")) + throw new InvalidTokenException(this); + throw new AuthenticationException(response.getError() + ": " + response.getErrorMessage()); + } + + return response; + } catch (IOException e) { + throw new AuthenticationException("Unable to connect to authentication server", e); + } catch (JsonParseException e) { + throw new AuthenticationException("Unable to parse server response", e); + } + } + + private boolean checkTokenValidity(Proxy proxy) { + if (accessToken == null) + return false; + + try { + makeRequest(ROUTE_VALIDATE, new ValidateRequest(accessToken, clientToken), proxy); + return true; + } catch (AuthenticationException e) { + return false; + } + } + + @Override + public String toString() { + return "YggdrasilAccount[username=" + getUsername() + "]"; + } + + private static final String BASE_URL = "https://authserver.mojang.com/"; + private static final URL ROUTE_AUTHENTICATE = NetworkUtils.toURL(BASE_URL + "authenticate"); + private static final URL ROUTE_REFRESH = NetworkUtils.toURL(BASE_URL + "refresh"); + private static final URL ROUTE_VALIDATE = NetworkUtils.toURL(BASE_URL + "validate"); + + static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken"; + static final String STORAGE_KEY_PROFILE_NAME = "displayName"; + static final String STORAGE_KEY_PROFILE_ID = "uuid"; + static final String STORAGE_KEY_PROFILE_PROPERTIES = "profileProperties"; + static final String STORAGE_KEY_USER_NAME = "username"; + static final String STORAGE_KEY_USER_ID = "userid"; + static final String STORAGE_KEY_USER_PROPERTIES = "userProperties"; + static final String STORAGE_KEY_CLIENT_TOKEN = "clientToken"; + + public static String randomToken() { + return UUIDTypeAdapter.fromUUID(UUID.randomUUID()); + } + + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(GameProfile.class, GameProfile.Serializer.INSTANCE) + .registerTypeAdapter(PropertyMap.class, PropertyMap.Serializer.INSTANCE) + .registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE) + .create(); + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java new file mode 100644 index 000000000..ef454ba57 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java @@ -0,0 +1,70 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.auth.yggdrasil; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jackhuang.hmcl.auth.AccountFactory; +import org.jackhuang.hmcl.util.Lang; +import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*; +import org.jackhuang.hmcl.util.UUIDTypeAdapter; + +/** + * + * @author huangyuhui + */ +public final class YggdrasilAccountFactory extends AccountFactory { + public static final YggdrasilAccountFactory INSTANCE = new YggdrasilAccountFactory(); + + private YggdrasilAccountFactory() { + } + + @Override + public YggdrasilAccount fromUsername(String username, String password) { + YggdrasilAccount account = new YggdrasilAccount(username); + account.setPassword(password); + return account; + } + + @Override + public YggdrasilAccount fromStorage(Map storage) { + String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class) + .orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME)); + + YggdrasilAccount account = new YggdrasilAccount(username); + account.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class, username)); + account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class, null)); + account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class) + .orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_CLIENT_TOKEN))); + + Lang.get(storage, STORAGE_KEY_USER_PROPERTIES, List.class) + .ifPresent(account.getUserProperties()::fromList); + Optional profileId = Lang.get(storage, STORAGE_KEY_PROFILE_ID, String.class); + Optional profileName = Lang.get(storage, STORAGE_KEY_PROFILE_NAME, String.class); + GameProfile profile = null; + if (profileId.isPresent() && profileName.isPresent()) { + profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get()); + Lang.get(storage, STORAGE_KEY_PROFILE_PROPERTIES, List.class) + .ifPresent(profile.getProperties()::fromList); + } + account.setSelectedProfile(profile); + return account; + } + +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/AbstractDependencyManager.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AbstractDependencyManager.java similarity index 69% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/AbstractDependencyManager.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/AbstractDependencyManager.java index 0f198a042..1776429a7 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/AbstractDependencyManager.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AbstractDependencyManager.java @@ -15,14 +15,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.download +package org.jackhuang.hmcl.download; -abstract class AbstractDependencyManager - : DependencyManager { - abstract val downloadProvider: DownloadProvider +/** + * + * @author huangyuhui + */ +public abstract class AbstractDependencyManager implements DependencyManager { - override fun getVersionList(id: String): VersionList<*> { - return downloadProvider.getVersionListById(id) + public abstract DownloadProvider getDownloadProvider(); + + @Override + public VersionList getVersionList(String id) { + return getDownloadProvider().getVersionListById(id); } - -} \ No newline at end of file +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java new file mode 100644 index 000000000..af982eef4 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java @@ -0,0 +1,88 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download; + +import org.jackhuang.hmcl.download.forge.ForgeVersionList; +import org.jackhuang.hmcl.download.game.GameVersionList; +import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList; +import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList; + +/** + * + * @author huang + */ +public class BMCLAPIDownloadProvider implements DownloadProvider { + + public static final BMCLAPIDownloadProvider INSTANCE = new BMCLAPIDownloadProvider(); + + private BMCLAPIDownloadProvider() { + } + + @Override + public String getLibraryBaseURL() { + return "http://bmclapi2.bangbang93.com/libraries/"; + } + + @Override + public String getVersionListURL() { + return "http://bmclapi2.bangbang93.com/mc/game/version_manifest.json"; + } + + @Override + public String getVersionBaseURL() { + return "http://bmclapi2.bangbang93.com/versions/"; + } + + @Override + public String getAssetIndexBaseURL() { + return "http://bmclapi2.bangbang93.com/indexes/"; + } + + @Override + public String getAssetBaseURL() { + return "http://bmclapi2.bangbang93.com/assets/"; + } + + @Override + public VersionList getVersionListById(String id) { + switch (id) { + case "game": + return GameVersionList.INSTANCE; + case "forge": + return ForgeVersionList.INSTANCE; + case "liteloader": + return LiteLoaderVersionList.INSTANCE; + case "optifine": + return OptiFineBMCLVersionList.INSTANCE; + default: + throw new IllegalArgumentException("Unrecognized version list id: " + id); + } + } + + @Override + public String injectURL(String baseURL) { + return baseURL + .replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com") + .replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com") + .replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries") + .replace("http://files.minecraftforge.net/maven", "https://bmclapi2.bangbang93.com/maven") + .replace("http://dl.liteloader.com/versions/versions.json", "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json") + .replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven"); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java new file mode 100644 index 000000000..9ee2bc341 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -0,0 +1,100 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download; + +import java.net.Proxy; +import org.jackhuang.hmcl.download.forge.ForgeInstallTask; +import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; +import org.jackhuang.hmcl.download.game.GameLoggingDownloadTask; +import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; +import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask; +import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; +import org.jackhuang.hmcl.game.DefaultGameRepository; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.ParallelTask; +import org.jackhuang.hmcl.task.Task; + +/** + * Note: This class has no state. + * + * @author huangyuhui + */ +public class DefaultDependencyManager extends AbstractDependencyManager { + + private final DefaultGameRepository repository; + private final DownloadProvider downloadProvider; + private final Proxy proxy; + + public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider) { + this(repository, downloadProvider, Proxy.NO_PROXY); + } + + public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, Proxy proxy) { + this.repository = repository; + this.downloadProvider = downloadProvider; + this.proxy = proxy; + } + + @Override + public DefaultGameRepository getGameRepository() { + return repository; + } + + @Override + public DownloadProvider getDownloadProvider() { + return downloadProvider; + } + + @Override + public Proxy getProxy() { + return proxy; + } + + @Override + public GameBuilder gameBuilder() { + return new DefaultGameBuilder(this); + } + + @Override + public Task checkGameCompletionAsync(Version version) { + return new ParallelTask( + new GameAssetDownloadTask(this, version), + new GameLoggingDownloadTask(this, version), + new GameLibrariesTask(this, version) + ); + } + + @Override + public Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) { + switch (libraryId) { + case "forge": + return new ForgeInstallTask(this, gameVersion, version, libraryVersion) + .then(variables -> new VersionJsonSaveTask(repository, variables.get("version"))); + case "liteloader": + return new LiteLoaderInstallTask(this, gameVersion, version, libraryVersion) + .then(variables -> new VersionJsonSaveTask(repository, variables.get("version"))); + case "optifine": + return new OptiFineInstallTask(this, gameVersion, version, libraryVersion) + .then(variables -> new VersionJsonSaveTask(repository, variables.get("version"))); + default: + throw new IllegalArgumentException("Library id " + libraryId + " is unrecognized."); + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java new file mode 100644 index 000000000..c1ec8df1a --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java @@ -0,0 +1,74 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download; + +import java.util.function.Function; +import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; +import org.jackhuang.hmcl.download.game.GameDownloadTask; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; +import org.jackhuang.hmcl.download.game.GameLoggingDownloadTask; +import org.jackhuang.hmcl.download.game.VersionJsonDownloadTask; +import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.ParallelTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.AutoTypingMap; +import org.jackhuang.hmcl.util.Constants; + +/** + * + * @author huangyuhui + */ +public class DefaultGameBuilder extends GameBuilder { + + private final DefaultDependencyManager dependencyManager; + private final DownloadProvider downloadProvider; + + public DefaultGameBuilder(DefaultDependencyManager dependencyManager) { + this.dependencyManager = dependencyManager; + this.downloadProvider = dependencyManager.getDownloadProvider(); + } + + @Override + public Task buildAsync() { + return new VersionJsonDownloadTask(gameVersion, dependencyManager).then(variables -> { + Version version = Constants.GSON.fromJson(variables.get(VersionJsonDownloadTask.ID), Version.class); + variables.set("version", version); + version = version.setId(name).setJar(null); + Task result = new ParallelTask( + new GameAssetDownloadTask(dependencyManager, version), + new GameLoggingDownloadTask(dependencyManager, version), + new GameDownloadTask(dependencyManager, version), + new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries. + ).with(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version)); // using [with] because download failure here are tolerant. + + if (toolVersions.containsKey("forge")) + result = result.then(libraryTaskHelper(gameVersion, "forge")); + if (toolVersions.containsKey("liteloader")) + result = result.then(libraryTaskHelper(gameVersion, "liteloader")); + if (toolVersions.containsKey("optifine")) + result = result.then(libraryTaskHelper(gameVersion, "optifine")); + return result; + }); + } + + private Function, Task> libraryTaskHelper(String gameVersion, String libraryId) { + return variables -> dependencyManager.installLibraryAsync(gameVersion, variables.get("version"), libraryId, toolVersions.get(libraryId)); + } + +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DependencyManager.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DependencyManager.java similarity index 78% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DependencyManager.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/DependencyManager.java index 32495bb63..fc00bf3cb 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DependencyManager.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DependencyManager.java @@ -15,27 +15,30 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.download +package org.jackhuang.hmcl.download; -import org.jackhuang.hmcl.game.GameRepository -import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.task.Task -import java.net.Proxy +import java.net.Proxy; +import org.jackhuang.hmcl.game.GameRepository; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; /** * Do everything that will connect to Internet. * Downloading Minecraft files. + * + * @author huangyuhui */ -interface DependencyManager { +public interface DependencyManager { + /** * The relied game repository. */ - val repository: GameRepository + GameRepository getGameRepository(); /** * The proxy that all network operations should go through. */ - val proxy: Proxy + Proxy getProxy(); /** * Check if the game is complete. @@ -43,12 +46,12 @@ interface DependencyManager { * * @return the task to check game completion. */ - fun checkGameCompletionAsync(version: Version): Task + Task checkGameCompletionAsync(Version version); /** * The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine. */ - fun gameBuilder(): GameBuilder + GameBuilder gameBuilder(); /** * Install a library to a version. @@ -60,7 +63,7 @@ interface DependencyManager { * @param libraryVersion the version of being installed library. * @return the task to install the specific library. */ - fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task + Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion); /** * Get registered version list. @@ -68,5 +71,5 @@ interface DependencyManager { * @param id the id of version list. i.e. game, forge, liteloader, optifine * @throws IllegalArgumentException if the version list of specific id is not found. */ - fun getVersionList(id: String): VersionList<*> -} \ No newline at end of file + VersionList getVersionList(String id); +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DownloadProvider.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java similarity index 80% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DownloadProvider.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java index 00d81b8a5..75610a353 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DownloadProvider.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java @@ -15,17 +15,24 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.download +package org.jackhuang.hmcl.download; /** * The service provider that provides Minecraft online file downloads. + * + * @author huangyuhui */ -interface DownloadProvider { - val libraryBaseURL: String - val versionListURL: String - val versionBaseURL: String - val assetIndexBaseURL: String - val assetBaseURL: String +public interface DownloadProvider { + + String getLibraryBaseURL(); + + String getVersionListURL(); + + String getVersionBaseURL(); + + String getAssetIndexBaseURL(); + + String getAssetBaseURL(); /** * Inject into original URL provided by Mojang and Forge. @@ -36,12 +43,13 @@ interface DownloadProvider { * @param baseURL original URL provided by Mojang and Forge. * @return the URL that is equivalent to [baseURL], but belongs to your own service provider. */ - fun injectURL(baseURL: String): String + String injectURL(String baseURL); /** * the specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine" + * * @param id the id of specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine" * @return the version list */ - fun getVersionListById(id: String): VersionList<*> -} \ No newline at end of file + VersionList getVersionListById(String id); +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/GameBuilder.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/GameBuilder.java similarity index 59% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/GameBuilder.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/GameBuilder.java index 897ea6c64..7fc9ef7c3 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/GameBuilder.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/GameBuilder.java @@ -15,45 +15,57 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.download +package org.jackhuang.hmcl.download; -import org.jackhuang.hmcl.task.Task +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jackhuang.hmcl.task.Task; /** * The builder which provide a task to build Minecraft environment. * * @author huangyuhui */ -abstract class GameBuilder { - var name: String = "" - protected var gameVersion: String = "" - protected var toolVersions = HashMap() +public abstract class GameBuilder { + + protected String name = ""; + protected String gameVersion = ""; + protected Map toolVersions = new HashMap<>(); + + public String getName() { + return name; + } /** * The new game version name, for .minecraft/. + * * @param name the name of new game version. */ - fun name(name: String): GameBuilder { - this.name = name - return this + public GameBuilder name(String name) { + this.name = Objects.requireNonNull(name); + return this; } - fun gameVersion(version: String): GameBuilder { - gameVersion = version - return this + public GameBuilder gameVersion(String version) { + this.gameVersion = Objects.requireNonNull(version); + return this; } /** * @param id the core library id. i.e. "forge", "liteloader", "optifine" * @param version the version of the core library. For documents, you can first try [VersionList.versions] */ - fun version(id: String, version: String): GameBuilder { - toolVersions[id] = version - return this + public GameBuilder version(String id, String version) { + if ("game".equals(id)) + gameVersion(version); + else + toolVersions.put(id, version); + return this; } /** * @return the task that can build thw whole Minecraft environment */ - abstract fun buildAsync(): Task -} \ No newline at end of file + public abstract Task buildAsync(); +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java new file mode 100644 index 000000000..a6c4b4f0a --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java @@ -0,0 +1,84 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download; + +import org.jackhuang.hmcl.download.forge.ForgeVersionList; +import org.jackhuang.hmcl.download.game.GameVersionList; +import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList; +import org.jackhuang.hmcl.download.optifine.OptiFineVersionList; + +/** + * @see {@link http://wiki.vg} + * @author huangyuhui + */ +public final class MojangDownloadProvider implements DownloadProvider { + + public static final MojangDownloadProvider INSTANCE = new MojangDownloadProvider(); + + private MojangDownloadProvider() { + } + + @Override + public String getLibraryBaseURL() { + return "https://libraries.minecraft.net/"; + } + + @Override + public String getVersionListURL() { + return "https://launchermeta.mojang.com/mc/game/version_manifest.json"; + } + + @Override + public String getVersionBaseURL() { + return "http://s3.amazonaws.com/Minecraft.Download/versions/"; + } + + @Override + public String getAssetIndexBaseURL() { + return "http://s3.amazonaws.com/Minecraft.Download/indexes/"; + } + + @Override + public String getAssetBaseURL() { + return "http://resources.download.minecraft.net/"; + } + + @Override + public VersionList getVersionListById(String id) { + switch (id) { + case "game": + return GameVersionList.INSTANCE; + case "forge": + return ForgeVersionList.INSTANCE; + case "liteloader": + return LiteLoaderVersionList.INSTANCE; + case "optifine": + return OptiFineVersionList.INSTANCE; + default: + throw new IllegalArgumentException("Unrecognized version list id: " + id); + } + } + + @Override + public String injectURL(String baseURL) { + if (baseURL.contains("net/minecraftforge/forge")) + return baseURL; + else + return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven"); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java new file mode 100644 index 000000000..361045ad5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java @@ -0,0 +1,96 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download; + +import java.util.Comparator; +import java.util.Objects; +import org.jackhuang.hmcl.util.VersionNumber; + +/** + * The remote version. + * + * @author huangyuhui + */ +public final class RemoteVersion implements Comparable> { + + private final String gameVersion; + private final String selfVersion; + private final String url; + private final T tag; + + /** + * Constructor. + * + * @param gameVersion the Minecraft version that this remote version suits. + * @param selfVersion the version string of the remote version. + * @param url the installer or universal jar URL. + * @param tag some necessary information for Installer Task. + */ + public RemoteVersion(String gameVersion, String selfVersion, String url, T tag) { + this.gameVersion = Objects.requireNonNull(gameVersion); + this.selfVersion = Objects.requireNonNull(selfVersion); + this.url = Objects.requireNonNull(url); + this.tag = tag; + } + + public String getGameVersion() { + return gameVersion; + } + + public String getSelfVersion() { + return selfVersion; + } + + public T getTag() { + return tag; + } + + public String getUrl() { + return url; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof RemoteVersion && Objects.equals(selfVersion, ((RemoteVersion) obj).selfVersion); + } + + @Override + public int hashCode() { + return selfVersion.hashCode(); + } + + @Override + public int compareTo(RemoteVersion o) { + // newer versions are smaller than older versions + return -selfVersion.compareTo(o.selfVersion); + } + + public static class RemoteVersionComparator implements Comparator> { + + public static final RemoteVersionComparator INSTANCE = new RemoteVersionComparator(); + + private RemoteVersionComparator() { + } + + @Override + public int compare(RemoteVersion o1, RemoteVersion o2) { + return -VersionNumber.asVersion(o1.selfVersion).compareTo(VersionNumber.asVersion(o2.selfVersion)); + } + + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/VersionList.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java similarity index 55% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/VersionList.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java index d62f8dbeb..e2679cdd4 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/VersionList.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/VersionList.java @@ -15,39 +15,48 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.download +package org.jackhuang.hmcl.download; -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.util.SimpleMultimap -import java.util.* -import kotlin.collections.HashMap +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Optional; +import java.util.TreeSet; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.SimpleMultimap; /** * The remote version list. - * @param T The type of RemoteVersion, the type of tags. + * + * @param T The type of {@code RemoteVersion}, the type of tags. + * + * @author huangyuhui */ -abstract class VersionList { +public abstract class VersionList { + /** * the remote version list. * key: game version. * values: corresponding remote versions. */ - protected val versions = SimpleMultimap>(::HashMap, ::TreeSet) + protected final SimpleMultimap> versions = new SimpleMultimap>(HashMap::new, TreeSet::new); /** * True if the version list has been loaded. */ - val loaded = versions.isNotEmpty + public boolean isLoaded() { + return !versions.isEmpty(); + } /** * @param downloadProvider DownloadProvider * @return the task to reload the remote version list. */ - abstract fun refreshAsync(downloadProvider: DownloadProvider): Task + public abstract Task refreshAsync(DownloadProvider downloadProvider); - private fun getVersionsImpl(gameVersion: String): Collection> { - val ans = versions[gameVersion] - return if (ans.isEmpty()) versions.values else ans + private Collection> getVersionsImpl(String gameVersion) { + Collection> ans = versions.get(gameVersion); + return ans.isEmpty() ? versions.values() : ans; } /** @@ -56,8 +65,8 @@ abstract class VersionList { * @param gameVersion the Minecraft version that remote versions belong to * @return the collection of specific remote versions */ - fun getVersions(gameVersion: String): Collection> { - return Collections.unmodifiableCollection(getVersionsImpl(gameVersion)) + public final Collection> getVersions(String gameVersion) { + return Collections.unmodifiableCollection(getVersionsImpl(gameVersion)); } /** @@ -67,13 +76,11 @@ abstract class VersionList { * @param remoteVersion the version of the remote version. * @return the specific remote version, null if it is not found. */ - fun getVersion(gameVersion: String, remoteVersion: String): RemoteVersion? { - var result : RemoteVersion? = null - versions[gameVersion].forEach { - if (it.selfVersion == remoteVersion) - result = it - } - return result + public final Optional> getVersion(String gameVersion, String remoteVersion) { + RemoteVersion result = null; + for (RemoteVersion it : versions.get(gameVersion)) + if (remoteVersion.equals(it.getSelfVersion())) + result = it; + return Optional.ofNullable(result); } - -} \ No newline at end of file +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java new file mode 100644 index 000000000..9a7e378ab --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -0,0 +1,140 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.forge; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.SimpleVersionProvider; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskResult; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.IOUtils; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * + * @author huangyuhui + */ +public final class ForgeInstallTask extends TaskResult { + + private final DefaultDependencyManager dependencyManager; + private final String gameVersion; + private final Version version; + private final String remoteVersion; + private final VersionList forgeVersionList; + private final File installer = new File("forge-installer.jar").getAbsoluteFile(); + private RemoteVersion remote; + private final List dependents = new LinkedList<>(); + private final List dependencies = new LinkedList<>(); + + private Task downloadFileTask() { + remote = forgeVersionList.getVersion(gameVersion, remoteVersion) + .orElseThrow(() -> new IllegalArgumentException("Remote forge version " + gameVersion + ", " + remoteVersion + " not found")); + return new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), installer); + } + + public ForgeInstallTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version, String remoteVersion) { + this.dependencyManager = dependencyManager; + this.gameVersion = gameVersion; + this.version = version; + this.remoteVersion = remoteVersion; + + forgeVersionList = dependencyManager.getVersionList("forge"); + + if (!forgeVersionList.isLoaded()) + dependents.add(forgeVersionList.refreshAsync(dependencyManager.getDownloadProvider()) + .then(s -> downloadFileTask())); + else + dependents.add(downloadFileTask()); + } + + @Override + public Collection getDependents() { + return dependents; + } + + @Override + public List getDependencies() { + return dependencies; + } + + @Override + public String getId() { + return "version"; + } + + @Override + public boolean isRelyingOnDependencies() { + return false; + } + + @Override + public void execute() throws Exception { + try (ZipFile zipFile = new ZipFile(installer)) { + InputStream stream = zipFile.getInputStream(zipFile.getEntry("install_profile.json")); + if (stream == null) + throw new IOException("Malformed forge installer file, install_profile.json does not exist."); + String json = IOUtils.readFullyAsString(stream); + InstallProfile installProfile = Constants.GSON.fromJson(json, InstallProfile.class); + if (installProfile == null) + throw new IOException("Malformed forge installer file, install_profile.json does not exist."); + + // unpack the universal jar in the installer file. + Library forgeLibrary = Library.fromName(installProfile.getInstall().getPath()); + File forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary); + if (!FileUtils.makeFile(forgeFile)) + throw new IOException("Cannot make directory " + forgeFile.getParent()); + + ZipEntry forgeEntry = zipFile.getEntry(installProfile.getInstall().getFilePath()); + try (InputStream is = zipFile.getInputStream(forgeEntry); OutputStream os = new FileOutputStream(forgeFile)) { + IOUtils.copyTo(is, os); + } + + // resolve the version + SimpleVersionProvider provider = new SimpleVersionProvider(); + provider.addVersion(version); + + setResult(installProfile.getVersionInfo() + .setInheritsFrom(version.getId()) + .resolve(provider) + .setId(version.getId()).setLogging(Collections.EMPTY_MAP)); + + dependencies.add(new GameLibrariesTask(dependencyManager, installProfile.getVersionInfo())); + } + + if (!installer.delete()) + throw new IOException("Unable to delete installer file" + installer); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java new file mode 100644 index 000000000..2323b43bd --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java @@ -0,0 +1,91 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.forge; + +import com.google.gson.JsonParseException; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class ForgeVersion implements Validation { + + private final String branch; + private final String mcversion; + private final String jobver; + private final String version; + private final int build; + private final double modified; + private final String[][] files; + + public ForgeVersion() { + this(null, null, null, null, 0, 0, null); + } + + public ForgeVersion(String branch, String mcversion, String jobver, String version, int build, double modified, String[][] files) { + this.branch = branch; + this.mcversion = mcversion; + this.jobver = jobver; + this.version = version; + this.build = build; + this.modified = modified; + this.files = files; + } + + public String getBranch() { + return branch; + } + + public String getGameVersion() { + return mcversion; + } + + public String getJobver() { + return jobver; + } + + public String getVersion() { + return version; + } + + public int getBuild() { + return build; + } + + public double getModified() { + return modified; + } + + public String[][] getFiles() { + return files; + } + + @Override + public void validate() throws JsonParseException { + if (files == null) + throw new JsonParseException("ForgeVersion files cannot be null"); + if (version == null) + throw new JsonParseException("ForgeVersion version cannot be null"); + if (mcversion == null) + throw new JsonParseException("ForgeVersion mcversion cannot be null"); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java new file mode 100644 index 000000000..f24e05552 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java @@ -0,0 +1,93 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.forge; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.VersionNumber; + +/** + * + * @author huangyuhui + */ +public final class ForgeVersionList extends VersionList { + + public static final ForgeVersionList INSTANCE = new ForgeVersionList(); + + private ForgeVersionList() { + } + + @Override + public Task refreshAsync(DownloadProvider downloadProvider) { + final GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(FORGE_LIST))); + final List dependents = Collections.singletonList(task); + return new Task() { + + @Override + public Collection getDependents() { + return dependents; + } + + @Override + public void execute() throws Exception { + ForgeVersionRoot root = Constants.GSON.fromJson(task.getResult(), ForgeVersionRoot.class); + if (root == null) + return; + versions.clear(); + + for (Map.Entry entry : root.getGameVersions().entrySet()) { + String gameVersion = VersionNumber.parseVersion(entry.getKey()); + if (gameVersion == null) + continue; + for (int v : entry.getValue()) { + ForgeVersion version = root.getNumber().get(v); + if (version == null) + continue; + String jar = null; + for (String[] file : version.getFiles()) + if (file.length > 1 && "installer".equals(file[1])) { + String classifier = version.getGameVersion() + "-" + version.getVersion() + + (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : ""); + String fileName = root.getArtifact() + "-" + classifier + "-" + file[1] + "." + file[0]; + jar = downloadProvider.injectURL(root.getWebPath() + classifier + "/" + fileName); + } + + if (jar == null) + continue; + versions.put(gameVersion, new RemoteVersion<>( + version.getGameVersion(), version.getVersion(), jar, null + )); + } + } + } + + }; + } + + public static final String FORGE_LIST = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/json"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java new file mode 100644 index 000000000..d7c19bac4 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java @@ -0,0 +1,102 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.forge; + +import com.google.gson.JsonParseException; +import java.util.Map; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class ForgeVersionRoot implements Validation { + + private final String artifact; + private final String webpath; + private final String adfly; + private final String homepage; + private final String name; + private final Map branches; + private final Map mcversion; + private final Map promos; + private final Map number; + + public ForgeVersionRoot() { + this(null, null, null, null, null, null, null, null, null); + } + + public ForgeVersionRoot(String artifact, String webpath, String adfly, String homepage, String name, Map branches, Map mcversion, Map promos, Map number) { + this.artifact = artifact; + this.webpath = webpath; + this.adfly = adfly; + this.homepage = homepage; + this.name = name; + this.branches = branches; + this.mcversion = mcversion; + this.promos = promos; + this.number = number; + } + + public String getArtifact() { + return artifact; + } + + public String getWebPath() { + return webpath; + } + + public String getAdfly() { + return adfly; + } + + public String getHomePage() { + return homepage; + } + + public String getName() { + return name; + } + + public Map getBranches() { + return branches; + } + + public Map getGameVersions() { + return mcversion; + } + + public Map getPromos() { + return promos; + } + + public Map getNumber() { + return number; + } + + @Override + public void validate() throws JsonParseException { + if (number == null) + throw new JsonParseException("ForgeVersionRoot number cannot be null"); + if (mcversion == null) + throw new JsonParseException("ForgeVersionRoot mcversion cannot be null"); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/Install.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/Install.java new file mode 100644 index 000000000..94d185a06 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/Install.java @@ -0,0 +1,91 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.forge; + +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class Install { + + private final String profileName; + private final String target; + private final String path; + private final String version; + private final String filePath; + private final String welcome; + private final String minecraft; + private final String mirrorList; + private final String logo; + + public Install() { + this(null, null, null, null, null, null, null, null, null); + } + + public Install(String profileName, String target, String path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) { + this.profileName = profileName; + this.target = target; + this.path = path; + this.version = version; + this.filePath = filePath; + this.welcome = welcome; + this.minecraft = minecraft; + this.mirrorList = mirrorList; + this.logo = logo; + } + + public String getProfileName() { + return profileName; + } + + public String getTarget() { + return target; + } + + public String getPath() { + return path; + } + + public String getVersion() { + return version; + } + + public String getFilePath() { + return filePath; + } + + public String getWelcome() { + return welcome; + } + + public String getMinecraft() { + return minecraft; + } + + public String getMirrorList() { + return mirrorList; + } + + public String getLogo() { + return logo; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/InstallProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/InstallProfile.java new file mode 100644 index 000000000..1a2958dbf --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/InstallProfile.java @@ -0,0 +1,60 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.forge; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class InstallProfile implements Validation { + + @SerializedName("install") + private final Install install; + + @SerializedName("versionInfo") + private final Version versionInfo; + + public InstallProfile(Install install, Version versionInfo) { + this.install = install; + this.versionInfo = versionInfo; + } + + public Install getInstall() { + return install; + } + + public Version getVersionInfo() { + return versionInfo; + } + + @Override + public void validate() throws JsonParseException { + if (install == null) + throw new JsonParseException("InstallProfile install cannot be null"); + + if (versionInfo == null) + throw new JsonParseException("InstallProfile versionInfo cannot be null"); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java new file mode 100644 index 000000000..b9fe2ca2b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java @@ -0,0 +1,111 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import org.jackhuang.hmcl.download.AbstractDependencyManager; +import org.jackhuang.hmcl.game.AssetIndexInfo; +import org.jackhuang.hmcl.game.AssetObject; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.DigestUtils; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * + * @author huangyuhui + */ +public final class GameAssetDownloadTask extends Task { + + private final AbstractDependencyManager dependencyManager; + private final Version version; + private final GameAssetRefreshTask refreshTask; + private final List dependents = new LinkedList<>(); + private final List dependencies = new LinkedList<>(); + + /** + * Constructor. + * + * @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository} + * @param version the resolved version + */ + public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version) { + this.dependencyManager = dependencyManager; + this.version = version; + this.refreshTask = new GameAssetRefreshTask(dependencyManager, version); + this.dependents.add(refreshTask); + } + + @Override + public Collection getDependents() { + return dependents; + } + + @Override + public List getDependencies() { + return dependencies; + } + + @Override + public void execute() throws Exception { + int size = refreshTask.getResult().size(); + for (Map.Entry entry : refreshTask.getResult()) { + File file = entry.getKey(); + AssetObject assetObject = entry.getValue(); + String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation(); + if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) { + Logging.LOG.log(Level.SEVERE, "Unable to create new file {0}, because parent directory cannot be created", file); + continue; + } + if (file.isDirectory()) + continue; + boolean flag = true; + int downloaded = 0; + try { + // check the checksum of file to ensure that the file is not need to re-download. + if (file.exists()) { + String sha1 = DigestUtils.sha1Hex(FileUtils.readBytes(file)); + if (sha1.equals(assetObject.getHash())) { + ++downloaded; + Logging.LOG.finest("File $file has been downloaded successfully, skipped downloading"); + updateProgress(downloaded, size); + continue; + } + } + } catch (IOException e) { + Logging.LOG.log(Level.WARNING, "Unable to get hash code of file " + file, e); + flag = !file.exists(); + } + if (flag) { + FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, dependencyManager.getProxy(), assetObject.getHash()); + task.setName(assetObject.getHash()); + dependencies.add(task); + } + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java new file mode 100644 index 000000000..ab4b1095b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java @@ -0,0 +1,72 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.download.AbstractDependencyManager; +import org.jackhuang.hmcl.game.AssetIndexInfo; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * This task is to download asset index file provided in minecraft.json. + * + * @author huangyuhui + */ +public final class GameAssetIndexDownloadTask extends Task { + + private final AbstractDependencyManager dependencyManager; + private final Version version; + private final List dependencies = new LinkedList<>(); + + /** + * Constructor. + * + * @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository} + * @param version the resolved version + */ + public GameAssetIndexDownloadTask(AbstractDependencyManager dependencyManager, Version version) { + this.dependencyManager = dependencyManager; + this.version = version; + } + + @Override + public List getDependencies() { + return dependencies; + } + + @Override + public void execute() throws Exception { + AssetIndexInfo assetIndexInfo = version.getAssetIndex(); + File assetDir = dependencyManager.getGameRepository().getAssetDirectory(version.getId(), assetIndexInfo.getId()); + if (FileUtils.makeDirectory(assetDir)) + throw new IOException("Cannot create directory: " + assetDir); + File assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId()); + dependencies.add(new FileDownloadTask( + NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(assetIndexInfo.getUrl())), + assetIndexFile, dependencyManager.getProxy() + )); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetRefreshTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetRefreshTask.java new file mode 100644 index 000000000..496746166 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetRefreshTask.java @@ -0,0 +1,88 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import java.io.File; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.download.AbstractDependencyManager; +import org.jackhuang.hmcl.game.AssetIndex; +import org.jackhuang.hmcl.game.AssetIndexInfo; +import org.jackhuang.hmcl.game.AssetObject; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskResult; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.Pair; + +/** + * This task is to extract all asset objects described in asset index json. + * + * @author huangyuhui + */ +public final class GameAssetRefreshTask extends TaskResult>> { + + private final AbstractDependencyManager dependencyManager; + private final Version version; + private final AssetIndexInfo assetIndexInfo; + private final File assetIndexFile; + private final List dependents = new LinkedList<>(); + + /** + * Constructor. + * + * @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository} + * @param version the resolved version + */ + public GameAssetRefreshTask(AbstractDependencyManager dependencyManager, Version version) { + this.dependencyManager = dependencyManager; + this.version = version; + this.assetIndexInfo = version.getAssetIndex(); + this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId()); + + if (!assetIndexFile.exists()) + dependents.add(new GameAssetIndexDownloadTask(dependencyManager, version)); + } + + @Override + public List getDependents() { + return dependents; + } + + @Override + public String getId() { + return ID; + } + + @Override + public void execute() throws Exception { + AssetIndex index = Constants.GSON.fromJson(FileUtils.readText(assetIndexFile), AssetIndex.class); + List> res = new LinkedList<>(); + int progress = 0; + if (index != null) + for (AssetObject assetObject : index.getObjects().values()) { + res.add(new Pair<>(dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject), assetObject)); + updateProgress(++progress, index.getObjects().size()); + } + setResult(res); + } + + public static final String ID = "game_asset_refresh_task"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java new file mode 100644 index 000000000..bd7f66fe0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java @@ -0,0 +1,60 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * + * @author huangyuhui + */ +public final class GameDownloadTask extends Task { + private final DefaultDependencyManager dependencyManager; + private final Version version; + private final List dependencies = new LinkedList<>(); + + public GameDownloadTask(DefaultDependencyManager dependencyManager, Version version) { + this.dependencyManager = dependencyManager; + this.version = version; + } + + @Override + public List getDependencies() { + return dependencies; + } + + @Override + public void execute() throws Exception { + File jar = dependencyManager.getGameRepository().getVersionJar(version); + + dependencies.add(new FileDownloadTask( + NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())), + jar, + dependencyManager.getProxy(), + version.getDownloadInfo().getSha1() + )); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java new file mode 100644 index 000000000..c3c8ea45b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java @@ -0,0 +1,70 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.download.AbstractDependencyManager; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * This task is to download game libraries. + * This task should be executed last(especially after game downloading, Forge, LiteLoader and OptiFine install task). + * + * @author huangyuhui + */ +public final class GameLibrariesTask extends Task { + + private final AbstractDependencyManager dependencyManager; + private final Version version; + private final List dependencies = new LinkedList<>(); + + /** + * Constructor. + * + * @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository} + * @param version the resolved version + */ + public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) { + this.dependencyManager = dependencyManager; + this.version = version; + } + + @Override + public List getDependencies() { + return dependencies; + } + + @Override + public void execute() throws Exception { + for (Library library : version.getLibraries()) + if (library.appliesToCurrentEnvironment()) { + File file = dependencyManager.getGameRepository().getLibraryFile(version, library); + if (!file.exists()) + dependencies.add(new FileDownloadTask( + NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(library.getDownload().getUrl())), + file, dependencyManager.getProxy(), library.getDownload().getSha1())); + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLoggingDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLoggingDownloadTask.java new file mode 100644 index 000000000..261c84558 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLoggingDownloadTask.java @@ -0,0 +1,70 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import java.io.File; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.download.DependencyManager; +import org.jackhuang.hmcl.game.DownloadType; +import org.jackhuang.hmcl.game.LoggingInfo; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * This task is to download log4j configuration file provided in minecraft.json. + * + * @author huangyuhui + */ +public final class GameLoggingDownloadTask extends Task { + + private final DependencyManager dependencyManager; + private final Version version; + private final List dependencies = new LinkedList<>(); + + /** + * Constructor. + * + * @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository} + * @param version the resolved version + */ + public GameLoggingDownloadTask(DependencyManager dependencyManager, Version version) { + this.dependencyManager = dependencyManager; + this.version = version; + } + + @Override + public Collection getDependencies() { + return dependencies; + } + + @Override + public void execute() throws Exception { + if (version.getLogging() == null || !version.getLogging().containsKey(DownloadType.CLIENT)) + return; + + LoggingInfo logging = version.getLogging().get(DownloadType.CLIENT); + File file = dependencyManager.getGameRepository().getLoggingObject(version.getId(), version.getAssetIndex().getId(), logging); + if (!file.exists()) + dependencies.add(new FileDownloadTask(NetworkUtils.toURL(logging.getFile().getUrl()), file, dependencyManager.getProxy())); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteLatestVersions.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteLatestVersions.java new file mode 100644 index 000000000..81755c704 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteLatestVersions.java @@ -0,0 +1,52 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class GameRemoteLatestVersions { + + @SerializedName("snapshot") + private final String snapshot; + + @SerializedName("release") + private final String release; + + public GameRemoteLatestVersions() { + this(null, null); + } + + public GameRemoteLatestVersions(String snapshot, String release) { + this.snapshot = snapshot; + this.release = release; + } + + public String getRelease() { + return release; + } + + public String getSnapshot() { + return snapshot; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersion.java new file mode 100644 index 000000000..747604c7f --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersion.java @@ -0,0 +1,92 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import java.util.Date; +import org.jackhuang.hmcl.game.ReleaseType; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +public final class GameRemoteVersion implements Validation { + + @SerializedName("id") + private final String gameVersion; + + @SerializedName("time") + private final Date time; + + @SerializedName("releaseTime") + private final Date releaseTime; + + @SerializedName("type") + private final ReleaseType type; + + @SerializedName("url") + private final String url; + + public GameRemoteVersion() { + this("", new Date(), new Date(), ReleaseType.UNKNOWN); + } + + public GameRemoteVersion(String gameVersion, Date time, Date releaseTime, ReleaseType type) { + this(gameVersion, time, releaseTime, type, Constants.DEFAULT_LIBRARY_URL + gameVersion + "/" + gameVersion + ".json"); + } + + public GameRemoteVersion(String gameVersion, Date time, Date releaseTime, ReleaseType type, String url) { + this.gameVersion = gameVersion; + this.time = time; + this.releaseTime = releaseTime; + this.type = type; + this.url = url; + } + + public String getGameVersion() { + return gameVersion; + } + + public Date getTime() { + return time; + } + + public Date getReleaseTime() { + return releaseTime; + } + + public ReleaseType getType() { + return type; + } + + public String getUrl() { + return url; + } + + @Override + public void validate() throws JsonParseException { + if (StringUtils.isBlank(gameVersion)) + throw new JsonParseException("GameRemoteVersion id cannot be blank"); + if (StringUtils.isBlank(url)) + throw new JsonParseException("GameRemoteVersion url cannot be blank"); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LoggingInfo.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersionTag.java similarity index 55% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LoggingInfo.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersionTag.java index b3967f865..b872d56e1 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LoggingInfo.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersionTag.java @@ -15,27 +15,36 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.game +package org.jackhuang.hmcl.download.game; -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.util.Immutable -import org.jackhuang.hmcl.util.Validation +import java.util.Date; +import org.jackhuang.hmcl.game.ReleaseType; +import org.jackhuang.hmcl.util.Immutable; +/** + * + * @author huangyuhui + */ @Immutable -class LoggingInfo @JvmOverloads constructor( - @SerializedName("file") - val file: IdDownloadInfo = IdDownloadInfo(), +public final class GameRemoteVersionTag { - @SerializedName("argument") - val argument: String = "", + private final ReleaseType type; + private final Date time; - @SerializedName("type") - val type: String = "" -): Validation { - - override fun validate() { - file.validate() - require(argument.isNotBlank(), { "LoggingInfo" }) - require(type.isNotBlank()) + public GameRemoteVersionTag() { + this(ReleaseType.UNKNOWN, new Date()); } -} \ No newline at end of file + + public GameRemoteVersionTag(ReleaseType type, Date time) { + this.type = type; + this.time = time; + } + + public Date getTime() { + return time; + } + + public ReleaseType getType() { + return type; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersions.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersions.java new file mode 100644 index 000000000..ce5a68353 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersions.java @@ -0,0 +1,55 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import com.google.gson.annotations.SerializedName; +import java.util.Collections; +import java.util.List; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class GameRemoteVersions { + + @SerializedName("versions") + private final List versions; + + @SerializedName("latest") + private final GameRemoteLatestVersions latest; + + public GameRemoteVersions() { + this(Collections.EMPTY_LIST, null); + } + + public GameRemoteVersions(List versions, GameRemoteLatestVersions latest) { + this.versions = versions; + this.latest = latest; + } + + public GameRemoteLatestVersions getLatest() { + return latest; + } + + public List getVersions() { + return versions; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVersionList.java new file mode 100644 index 000000000..48f89cfe3 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVersionList.java @@ -0,0 +1,71 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import java.util.Collection; +import java.util.Collections; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.jackhuang.hmcl.util.VersionNumber; + +/** + * + * @author huangyuhui + */ +public final class GameVersionList extends VersionList { + + public static final GameVersionList INSTANCE = new GameVersionList(); + + private GameVersionList() { + } + + @Override + public Task refreshAsync(DownloadProvider downloadProvider) { + GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.getVersionListURL())); + return new Task() { + @Override + public Collection getDependents() { + return Collections.singleton(task); + } + + @Override + public void execute() throws Exception { + versions.clear(); + + GameRemoteVersions root = Constants.GSON.fromJson(task.getResult(), GameRemoteVersions.class); + for (GameRemoteVersion remoteVersion : root.getVersions()) { + String gameVersion = VersionNumber.parseVersion(remoteVersion.getGameVersion()); + if (gameVersion == null) + continue; + versions.put(gameVersion, new RemoteVersion<>( + remoteVersion.getGameVersion(), + remoteVersion.getGameVersion(), + remoteVersion.getUrl(), + new GameRemoteVersionTag(remoteVersion.getType(), remoteVersion.getReleaseTime())) + ); + } + } + }; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonDownloadTask.java new file mode 100644 index 000000000..f8465e981 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonDownloadTask.java @@ -0,0 +1,70 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import java.net.Proxy; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * + * @author huangyuhui + */ +public final class VersionJsonDownloadTask extends Task { + private final String gameVersion; + private final DefaultDependencyManager dependencyManager; + private final List dependents = new LinkedList<>(); + private final List dependencies = new LinkedList<>(); + private final VersionList gameVersionList; + + public VersionJsonDownloadTask(String gameVersion, DefaultDependencyManager dependencyManager) { + this.gameVersion = gameVersion; + this.dependencyManager = dependencyManager; + this.gameVersionList = dependencyManager.getVersionList("game"); + + if (!gameVersionList.isLoaded()) + dependents.add(gameVersionList.refreshAsync(dependencyManager.getDownloadProvider())); + } + + @Override + public Collection getDependencies() { + return dependencies; + } + + @Override + public Collection getDependents() { + return dependents; + } + + @Override + public void execute() throws Exception { + RemoteVersion remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst() + .orElseThrow(() -> new IllegalStateException("Cannot find specific version "+gameVersion+" in remote repository")); + String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl()); + dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL), Proxy.NO_PROXY, ID)); + } + + public static final String ID = "raw_version_json"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonSaveTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonSaveTask.java new file mode 100644 index 000000000..37f27f160 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/VersionJsonSaveTask.java @@ -0,0 +1,56 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.game; + +import java.io.File; +import java.io.IOException; +import org.jackhuang.hmcl.game.DefaultGameRepository; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.FileUtils; + +/** + * This task is to save the version json. + * + * @author huangyuhui + */ +public final class VersionJsonSaveTask extends Task { + + private final DefaultGameRepository repository; + private final Version version; + + /** + * Constructor. + * + * @param repository the game repository + * @param version the **resolved** version + */ + public VersionJsonSaveTask(DefaultGameRepository repository, Version version) { + this.repository = repository; + this.version = version; + } + + @Override + public void execute() throws Exception { + File json = repository.getVersionJson(version.getId()).getAbsoluteFile(); + if (!FileUtils.makeFile(json)) + throw new IOException("Cannot create file " + json); + FileUtils.writeText(json, Constants.GSON.toJson(version)); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBranch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBranch.java new file mode 100644 index 000000000..35ad64af6 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderBranch.java @@ -0,0 +1,57 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.liteloader; + +import com.google.gson.annotations.SerializedName; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class LiteLoaderBranch { + + @SerializedName("libraries") + private final Collection libraries; + + @SerializedName("com.mumfrey:liteloader") + private final Map liteLoader; + + public LiteLoaderBranch() { + this(Collections.EMPTY_SET, Collections.EMPTY_MAP); + } + + public LiteLoaderBranch(Collection libraries, Map liteLoader) { + this.libraries = libraries; + this.liteLoader = liteLoader; + } + + public Collection getLibraries() { + return Collections.unmodifiableCollection(libraries); + } + + public Map getLiteLoader() { + return Collections.unmodifiableMap(liteLoader); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderGameVersions.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderGameVersions.java new file mode 100644 index 000000000..4a1dff251 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderGameVersions.java @@ -0,0 +1,61 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.liteloader; + +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class LiteLoaderGameVersions { + + @SerializedName("repo") + private final LiteLoaderRepository repoitory; + + @SerializedName("artefacts") + private final LiteLoaderBranch artifacts; + + @SerializedName("snapshots") + private final LiteLoaderBranch snapshots; + + public LiteLoaderGameVersions() { + this(null, null, null); + } + + public LiteLoaderGameVersions(LiteLoaderRepository repoitory, LiteLoaderBranch artifacts, LiteLoaderBranch snapshots) { + this.repoitory = repoitory; + this.artifacts = artifacts; + this.snapshots = snapshots; + } + + public LiteLoaderRepository getRepoitory() { + return repoitory; + } + + public LiteLoaderBranch getArtifacts() { + return artifacts; + } + + public LiteLoaderBranch getSnapshots() { + return snapshots; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java new file mode 100644 index 000000000..c96be49b6 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java @@ -0,0 +1,111 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.liteloader; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.game.LibrariesDownloadInfo; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.LibraryDownloadInfo; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskResult; +import org.jackhuang.hmcl.util.Lang; + +/** + * Note: LiteLoader must be installed after Forge. + * + * @author huangyuhui + */ +public final class LiteLoaderInstallTask extends TaskResult { + + private final DefaultDependencyManager dependencyManager; + private final String gameVersion; + private final Version version; + private final String remoteVersion; + private final LiteLoaderVersionList liteLoaderVersionList; + private RemoteVersion remote; + private final List dependents = new LinkedList<>(); + private final List dependencies = new LinkedList<>(); + + private void doRemote() { + remote = liteLoaderVersionList.getVersion(gameVersion, remoteVersion) + .orElseThrow(() -> new IllegalArgumentException("Remote LiteLoader version " + gameVersion + ", " + remoteVersion + " not found")); + } + + public LiteLoaderInstallTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version, String remoteVersion) { + this.dependencyManager = dependencyManager; + this.gameVersion = gameVersion; + this.version = version; + this.remoteVersion = remoteVersion; + + liteLoaderVersionList = (LiteLoaderVersionList) dependencyManager.getVersionList("liteloader"); + + if (!liteLoaderVersionList.isLoaded()) + dependents.add(liteLoaderVersionList.refreshAsync(dependencyManager.getDownloadProvider()) + .then(s -> { + doRemote(); + return null; + })); + else + doRemote(); + } + + @Override + public Collection getDependents() { + return dependents; + } + + @Override + public Collection getDependencies() { + return dependencies; + } + + @Override + public String getId() { + return "version"; + } + + @Override + public void execute() throws Exception { + Library library = new Library( + "com.mumfrey", "liteloader", remote.getSelfVersion(), null, + "http://dl.liteloader.com/versions/", + new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl())) + ); + + Version tempVersion = version.setLibraries(Lang.merge(remote.getTag().getLibraries(), Arrays.asList(library))); + setResult(version + .setMainClass("net.minecraft.launchwrapper.Launch") + .setLibraries(Lang.merge(tempVersion.getLibraries(), version.getLibraries())) + .setLogging(Collections.EMPTY_MAP) + .setMinecraftArguments(version.getMinecraftArguments().orElse("") + " --tweakClass " + remote.getTag().getTweakClass()) + //.setArguments(Arguments.addGameArguments(Lang.get(version.getArguments()), "--tweakClass", remote.getTag().getTweakClass())) + ); + + dependencies.add(new GameLibrariesTask(dependencyManager, tempVersion)); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemoteVersionTag.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemoteVersionTag.java new file mode 100644 index 000000000..8fc3ef640 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemoteVersionTag.java @@ -0,0 +1,49 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.liteloader; + +import java.util.Collection; +import java.util.Collections; +import org.jackhuang.hmcl.game.Library; + +/** + * + * @author huangyuhui + */ +public final class LiteLoaderRemoteVersionTag { + private final String tweakClass; + private final Collection libraries; + + public LiteLoaderRemoteVersionTag() { + this("", Collections.EMPTY_SET); + } + + public LiteLoaderRemoteVersionTag(String tweakClass, Collection libraries) { + this.tweakClass = tweakClass; + this.libraries = libraries; + } + + public Collection getLibraries() { + return libraries; + } + + public String getTweakClass() { + return tweakClass; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRepository.java new file mode 100644 index 000000000..0f106d4f9 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRepository.java @@ -0,0 +1,69 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.liteloader; + +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class LiteLoaderRepository { + + @SerializedName("stream") + private final String stream; + + @SerializedName("type") + private final String type; + + @SerializedName("url") + private final String url; + + @SerializedName("classifier") + private final String classifier; + + public LiteLoaderRepository() { + this("", "", "", ""); + } + + public LiteLoaderRepository(String stream, String type, String url, String classifier) { + this.stream = stream; + this.type = type; + this.url = url; + this.classifier = classifier; + } + + public String getStream() { + return stream; + } + + public String getType() { + return type; + } + + public String getUrl() { + return url; + } + + public String getClassifier() { + return classifier; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersion.java new file mode 100644 index 000000000..95bc3ba11 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersion.java @@ -0,0 +1,81 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.liteloader; + +import java.util.Collection; +import java.util.Collections; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class LiteLoaderVersion { + private final String tweakClass; + private final String file; + private final String version; + private final String md5; + private final String timestamp; + private final int lastSuccessfulBuild; + private final Collection libraries; + + public LiteLoaderVersion() { + this("", "", "", "", "", 0, Collections.EMPTY_SET); + } + + public LiteLoaderVersion(String tweakClass, String file, String version, String md5, String timestamp, int lastSuccessfulBuild, Collection libraries) { + this.tweakClass = tweakClass; + this.file = file; + this.version = version; + this.md5 = md5; + this.timestamp = timestamp; + this.lastSuccessfulBuild = lastSuccessfulBuild; + this.libraries = libraries; + } + + public String getTweakClass() { + return tweakClass; + } + + public String getFile() { + return file; + } + + public String getVersion() { + return version; + } + + public String getMd5() { + return md5; + } + + public String getTimestamp() { + return timestamp; + } + + public int getLastSuccessfulBuild() { + return lastSuccessfulBuild; + } + + public Collection getLibraries() { + return Collections.unmodifiableCollection(libraries); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.java new file mode 100644 index 000000000..e883f8e3b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.java @@ -0,0 +1,91 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.liteloader; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.jackhuang.hmcl.util.VersionNumber; + +/** + * + * @author huangyuhui + */ +public final class LiteLoaderVersionList extends VersionList { + + public static final LiteLoaderVersionList INSTANCE = new LiteLoaderVersionList(); + + private LiteLoaderVersionList() { + } + + @Override + public Task refreshAsync(DownloadProvider downloadProvider) { + GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST))); + return new Task() { + @Override + public Collection getDependents() { + return Collections.singleton(task); + } + + @Override + public void execute() throws Exception { + LiteLoaderVersionsRoot root = Constants.GSON.fromJson(task.getResult(), LiteLoaderVersionsRoot.class); + versions.clear(); + + for (Map.Entry entry : root.getVersions().entrySet()) { + String gameVersion = entry.getKey(); + LiteLoaderGameVersions liteLoader = entry.getValue(); + String gg = VersionNumber.parseVersion(gameVersion); + if (gg == null) + continue; + doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false); + doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true); + } + } + + private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) { + if (branch == null || repository == null) + return; + + for (Map.Entry entry : branch.getLiteLoader().entrySet()) { + String branchName = entry.getKey(); + LiteLoaderVersion v = entry.getValue(); + if ("latest".equals(branchName)) + continue; + + versions.put(key, new RemoteVersion<>(gameVersion, + v.getVersion().replace("SNAPSHOT", "SNAPSHOT-" + v.getLastSuccessfulBuild()), + snapshot + ? "http://jenkins.liteloader.com/LiteLoader " + gameVersion + "/lastSuccessfulBuild/artifact/build/libs/liteloader-" + v.getVersion() + "-release.jar" + : downloadProvider.injectURL(repository.getUrl() + "com/mumfrey/liteloader/" + gameVersion + "/" + v.getFile()), + new LiteLoaderRemoteVersionTag(v.getTweakClass(), v.getLibraries()) + )); + } + } + }; + } + + public static final String LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsMeta.java new file mode 100644 index 000000000..b5fafa97b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsMeta.java @@ -0,0 +1,61 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.liteloader; + +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class LiteLoaderVersionsMeta { + + @SerializedName("description") + private final String description; + + @SerializedName("authors") + private final String authors; + + @SerializedName("url") + private final String url; + + public LiteLoaderVersionsMeta() { + this("", "", ""); + } + + public LiteLoaderVersionsMeta(String description, String authors, String url) { + this.description = description; + this.authors = authors; + this.url = url; + } + + public String getDescription() { + return description; + } + + public String getAuthors() { + return authors; + } + + public String getUrl() { + return url; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsRoot.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsRoot.java new file mode 100644 index 000000000..c4b306e65 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionsRoot.java @@ -0,0 +1,55 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.liteloader; + +import com.google.gson.annotations.SerializedName; +import java.util.Collections; +import java.util.Map; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class LiteLoaderVersionsRoot { + + @SerializedName("versions") + private final Map versions; + + @SerializedName("meta") + private final LiteLoaderVersionsMeta meta; + + public LiteLoaderVersionsRoot() { + this(Collections.EMPTY_MAP, null); + } + + public LiteLoaderVersionsRoot(Map versions, LiteLoaderVersionsMeta meta) { + this.versions = versions; + this.meta = meta; + } + + public Map getVersions() { + return Collections.unmodifiableMap(versions); + } + + public LiteLoaderVersionsMeta getMeta() { + return meta; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java new file mode 100644 index 000000000..66d2a1d2d --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java @@ -0,0 +1,79 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.optifine; + +import com.google.gson.reflect.TypeToken; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.VersionNumber; + +/** + * + * @author huangyuhui + */ +public final class OptiFineBMCLVersionList extends VersionList { + + public static final OptiFineBMCLVersionList INSTANCE = new OptiFineBMCLVersionList(); + + private OptiFineBMCLVersionList() { + } + + @Override + public Task refreshAsync(DownloadProvider downloadProvider) { + GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi.bangbang93.com/optifine/versionlist")); + return new Task() { + @Override + public Collection getDependents() { + return Collections.singleton(task); + } + + @Override + public void execute() throws Exception { + versions.clear(); + Set duplicates = new HashSet<>(); + List root = Constants.GSON.fromJson(task.getResult(), new TypeToken>() { + }.getType()); + for (OptiFineVersion element : root) { + String version = element.getType(); + if (version == null) + continue; + String mirror = "http://bmclapi2.bangbang93.com/optifine/" + element.getGameVersion() + "/" + element.getType() + "/" + element.getPatch(); + if (!duplicates.add(mirror)) + continue; + + if (StringUtils.isBlank(element.getGameVersion())) + continue; + String gameVersion = VersionNumber.parseVersion(element.getGameVersion()); + versions.put(gameVersion, new RemoteVersion<>(gameVersion, version, mirror, null)); + } + } + }; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineDownloadFormatter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineDownloadFormatter.java new file mode 100644 index 000000000..ece0a5843 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineDownloadFormatter.java @@ -0,0 +1,56 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.optifine; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.jackhuang.hmcl.task.TaskResult; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * + * @author huangyuhui + */ +public final class OptiFineDownloadFormatter extends TaskResult { + + private final String url; + + public OptiFineDownloadFormatter(String url) { + this.url = url; + } + + @Override + public void execute() throws Exception { + String result = null; + String content = NetworkUtils.doGet(NetworkUtils.toURL(url)); + Matcher m = PATTERN.matcher(content); + while (m.find()) + result = m.group(1); + if (result == null) + throw new IllegalStateException("Cannot find version in " + content); + setResult("http://optifine.net/downloadx?f=OptiFine" + result); + } + + @Override + public String getId() { + return ID; + } + + public static final String ID = "optifine_formatter"; + private static final Pattern PATTERN = Pattern.compile("\"downloadx\\?f=OptiFine(.*)\""); +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java new file mode 100644 index 000000000..807f99b42 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java @@ -0,0 +1,142 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.optifine; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; +import org.jackhuang.hmcl.game.Argument; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.game.LibrariesDownloadInfo; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.LibraryDownloadInfo; +import org.jackhuang.hmcl.game.StringArgument; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskResult; +import org.jackhuang.hmcl.util.Lang; + +/** + * Note: OptiFine should be installed in the end. + * + * @author huangyuhui + */ +public final class OptiFineInstallTask extends TaskResult { + + private final DefaultDependencyManager dependencyManager; + private final String gameVersion; + private final Version version; + private final String remoteVersion; + private final VersionList optiFineVersionList; + private RemoteVersion remote; + private final List dependents = new LinkedList<>(); + private final List dependencies = new LinkedList<>(); + + private void doRemote() { + remote = optiFineVersionList.getVersion(gameVersion, remoteVersion) + .orElseThrow(() -> new IllegalArgumentException("Remote OptiFine version " + gameVersion + ", " + remoteVersion + " not found")); + } + + public OptiFineInstallTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version, String remoteVersion) { + this.dependencyManager = dependencyManager; + this.gameVersion = gameVersion; + this.version = version; + this.remoteVersion = remoteVersion; + + optiFineVersionList = dependencyManager.getVersionList("optifine"); + + if (!optiFineVersionList.isLoaded()) + dependents.add(optiFineVersionList.refreshAsync(dependencyManager.getDownloadProvider()) + .then(s -> { + doRemote(); + return null; + })); + else + doRemote(); + } + + @Override + public Collection getDependents() { + return dependents; + } + + @Override + public Collection getDependencies() { + return dependencies; + } + + @Override + public String getId() { + return "version"; + } + + @Override + public boolean isRelyingOnDependencies() { + return false; + } + + @Override + public void execute() throws Exception { + Library library = new Library( + "net.optifine", "optifine", remoteVersion, null, null, + new LibrariesDownloadInfo(new LibraryDownloadInfo( + "net/optifine/optifine/" + remoteVersion + "/optifine-" + remoteVersion + ".jar", + remote.getUrl())), true + ); + + List libraries = new LinkedList<>(); + libraries.add(library); + + boolean hasFMLTweaker = false; + if (version.getMinecraftArguments().isPresent() && version.getMinecraftArguments().get().contains("FMLTweaker")) + hasFMLTweaker = true; + if (version.getArguments().isPresent()) { + List game = version.getArguments().get().getGame(); + if (game.stream().anyMatch(arg -> arg.toString(Collections.EMPTY_MAP, Collections.EMPTY_MAP).contains("FMLTweaker"))) + hasFMLTweaker = true; + } + + /*Arguments arguments = Lang.get(version.getArguments()); + + if (!hasFMLTweaker) + arguments = Arguments.addGameArguments(arguments, "--tweakClass", "optifine.OptiFineTweaker"); + */ + String minecraftArguments = version.getMinecraftArguments().orElse(""); + if (!hasFMLTweaker) + minecraftArguments = minecraftArguments + " --tweakClass optifine.OptiFineTweaker"; + + if (version.getMainClass() == null || !version.getMainClass().startsWith("net.minecraft.launchwrapper.")) + libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12")); + + setResult(version + .setLibraries(Lang.merge(version.getLibraries(), libraries)) + .setMainClass("net.minecraft.launchwrapper.Launch") + .setMinecraftArguments(minecraftArguments) + //.setArguments(arguments) + ); + + dependencies.add(new GameLibrariesTask(dependencyManager, version.setLibraries(libraries))); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersion.java new file mode 100644 index 000000000..fbe460a78 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersion.java @@ -0,0 +1,90 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.optifine; + +import com.google.gson.annotations.SerializedName; + +/** + * + * @author huangyuhui + */ +public final class OptiFineVersion { + + @SerializedName("dl") + private final String downloadLink; + + @SerializedName("ver") + private final String version; + + @SerializedName("date") + private final String date; + + @SerializedName("type") + private final String type; + + @SerializedName("patch") + private final String patch; + + @SerializedName("mirror") + private final String mirror; + + @SerializedName("mcversion") + private final String gameVersion; + + public OptiFineVersion() { + this(null, null, null, null, null, null, null); + } + + public OptiFineVersion(String downloadLink, String version, String date, String type, String patch, String mirror, String gameVersion) { + this.downloadLink = downloadLink; + this.version = version; + this.date = date; + this.type = type; + this.patch = patch; + this.mirror = mirror; + this.gameVersion = gameVersion; + } + + public String getDownloadLink() { + return downloadLink; + } + + public String getVersion() { + return version; + } + + public String getDate() { + return date; + } + + public String getType() { + return type; + } + + public String getPatch() { + return patch; + } + + public String getMirror() { + return mirror; + } + + public String getGameVersion() { + return gameVersion; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersionList.java new file mode 100644 index 000000000..38c2d0fa0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersionList.java @@ -0,0 +1,96 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.download.optifine; + +import java.io.ByteArrayInputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +/** + * + * @author huangyuhui + */ +public final class OptiFineVersionList extends VersionList { + + private static final Pattern PATTERN = Pattern.compile("OptiFine (.*?) "); + public static final OptiFineVersionList INSTANCE = new OptiFineVersionList(); + + private OptiFineVersionList() { + } + + @Override + public Task refreshAsync(DownloadProvider downloadProvider) { + GetTask task = new GetTask(NetworkUtils.toURL("http://optifine.net/downloads")); + return new Task() { + @Override + public Collection getDependents() { + return Collections.singleton(task); + } + + @Override + public void execute() throws Exception { + versions.clear(); + + String html = task.getResult().replace(" ", " ").replace(">", ">").replace("<", "<").replace("
", "
"); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = factory.newDocumentBuilder(); + Document doc = db.parse(new ByteArrayInputStream(html.getBytes("UTF-8"))); + Element r = doc.getDocumentElement(); + NodeList tables = r.getElementsByTagName("table"); + for (int i = 0; i < tables.getLength(); i++) { + Element e = (Element) tables.item(i); + if ("downloadTable".equals(e.getAttribute("class"))) { + NodeList tr = e.getElementsByTagName("tr"); + for (int k = 0; k < tr.getLength(); k++) { + NodeList downloadLine = ((Element) tr.item(k)).getElementsByTagName("td"); + String url = null, version = null, gameVersion = null; + for (int j = 0; j < downloadLine.getLength(); j++) { + Element td = (Element) downloadLine.item(j); + if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineMirror")) + url = ((Element) td.getElementsByTagName("a").item(0)).getAttribute("href"); + if (td.getAttribute("class") != null && td.getAttribute("class").startsWith("downloadLineFile")) + version = td.getTextContent(); + } + Matcher matcher = PATTERN.matcher(version); + while (matcher.find()) + gameVersion = matcher.group(1); + if (gameVersion == null || version == null || url == null) + continue; + versions.put(gameVersion, new RemoteVersion<>(gameVersion, version, url, null)); + } + } + } + } + }; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/Event.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/Event.java new file mode 100644 index 000000000..4013e3f5b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/Event.java @@ -0,0 +1,85 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import java.util.EventObject; + +/** + * + * @author huangyuhui + */ +public class Event extends EventObject { + + public Event(Object source) { + super(source); + } + + private boolean canceled; + + /** + * true if this event is canceled. + * + * @throws UnsupportedOperationException if trying to cancel a non-cancelable event. + */ + public final boolean isCanceled() { + return canceled; + } + + /** + * + * @param canceled new value + * @throws UnsupportedOperationException if trying to cancel a non-cancelable event. + */ + public final void setCanceled(boolean canceled) { + if (!isCancelable()) + throw new UnsupportedOperationException("Attempted to cancel a non-cancelable event: " + getClass()); + this.canceled = canceled; + } + + /** + * true if this Event this cancelable. + */ + public boolean isCancelable() { + return false; + } + + public boolean hasResult() { + return false; + } + + private Result result; + + /** + * Retutns the value set as the result of this event + */ + public Result getResult() { + return result; + } + + public void setResult(Result result) { + if (!hasResult()) + throw new UnsupportedOperationException("Attempted to set result on a no result event: " + this.getClass() + " of type."); + this.result = result; + } + + public enum Result { + DENY, + DEFAULT, + ALLOW + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventBus.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventBus.java new file mode 100644 index 000000000..b5cb03298 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventBus.java @@ -0,0 +1,43 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import java.util.EventObject; +import java.util.HashMap; +import org.jackhuang.hmcl.task.Schedulers; + +/** + * + * @author huangyuhui + */ +public final class EventBus { + + private final HashMap, EventManager> events = new HashMap<>(); + + public EventManager channel(Class clazz) { + if (!events.containsKey(clazz)) + events.put(clazz, new EventManager<>(Schedulers.computation())); + return (EventManager) events.get(clazz); + } + + public void fireEvent(EventObject obj) { + channel((Class) obj.getClass()).fireEvent(obj); + } + + public static final EventBus EVENT_BUS = new EventBus(); +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventManager.java new file mode 100644 index 000000000..ee0048521 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventManager.java @@ -0,0 +1,85 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import java.util.EnumMap; +import java.util.EventObject; +import java.util.HashSet; +import java.util.function.Consumer; +import org.jackhuang.hmcl.task.Scheduler; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.util.SimpleMultimap; + +/** + * + * @author huangyuhui + */ +public final class EventManager { + + private final Scheduler scheduler; + private final SimpleMultimap> handlers + = new SimpleMultimap<>(() -> new EnumMap<>(EventPriority.class), HashSet::new); + private final SimpleMultimap handlers2 + = new SimpleMultimap<>(() -> new EnumMap<>(EventPriority.class), HashSet::new); + + public EventManager() { + this(Schedulers.immediate()); + } + + public EventManager(Scheduler scheduler) { + this.scheduler = scheduler; + } + + public void register(Consumer consumer) { + register(consumer, EventPriority.NORMAL); + } + + public void register(Consumer consumer, EventPriority priority) { + if (!handlers.get(priority).contains(consumer)) + handlers.put(priority, consumer); + } + + public void register(Runnable runnable) { + register(runnable, EventPriority.NORMAL); + } + + public void register(Runnable runnable, EventPriority priority) { + if (!handlers2.get(priority).contains(runnable)) + handlers2.put(priority, runnable); + } + + public void unregister(Consumer consumer) { + handlers.removeValue(consumer); + } + + public void unregister(Runnable runnable) { + handlers2.removeValue(runnable); + } + + public void fireEvent(T event) { + scheduler.schedule(() -> { + for (EventPriority priority : EventPriority.values()) { + for (Consumer handler : handlers.get(priority)) + handler.accept(event); + for (Runnable runnable : handlers2.get(priority)) + runnable.run(); + } + }); + } + +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventPriority.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventPriority.java similarity index 89% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventPriority.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventPriority.java index c6beb68d9..7a91df59d 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventPriority.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/EventPriority.java @@ -15,12 +15,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.event +package org.jackhuang.hmcl.event; -enum class EventPriority { +/** + * + * @author huangyuhui + */ +public enum EventPriority { HIGHEST, HIGH, NORMAL, LOW, LOWEST -} \ No newline at end of file +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/FailedEvent.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/FailedEvent.java new file mode 100644 index 000000000..5b1df9c16 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/FailedEvent.java @@ -0,0 +1,49 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import java.util.EventObject; + +/** + * + * @author huang + */ +public class FailedEvent extends EventObject { + + private final int failedTime; + private T newResult; + + public FailedEvent(Object source, int failedTime, T newResult) { + super(source); + this.failedTime = failedTime; + this.newResult = newResult; + } + + public int getFailedTime() { + return failedTime; + } + + public T getNewResult() { + return newResult; + } + + public void setNewResult(T newResult) { + this.newResult = newResult; + } + +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventBus.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/JVMLaunchFailedEvent.java similarity index 53% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventBus.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/event/JVMLaunchFailedEvent.java index 2ed9ce387..0e6f3ea09 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventBus.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/JVMLaunchFailedEvent.java @@ -15,27 +15,30 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.event +package org.jackhuang.hmcl.event; -import org.jackhuang.hmcl.task.Scheduler -import java.util.* +import java.util.EventObject; +import org.jackhuang.hmcl.util.ManagedProcess; -class EventBus { - private val events = HashMap, EventManager<*>>() +/** + * This event gets fired when we launch the JVM and it got crashed. + *
+ * This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS] + * + * @param source [org.jackhuang.hmcl.launch.ExitWaiter] + * @param value the crashed process. + * @author huangyuhui + */ +public class JVMLaunchFailedEvent extends EventObject { - @Suppress("UNCHECKED_CAST") - fun channel(classOfT: Class): EventManager { - if (!events.containsKey(classOfT)) - events.put(classOfT, EventManager(Scheduler.COMPUTATION)) - return events[classOfT] as EventManager + private final ManagedProcess process; + + public JVMLaunchFailedEvent(Object source, ManagedProcess process) { + super(source); + this.process = process; } - inline fun channel() = channel(T::class.java) - - fun fireEvent(obj: EventObject) { - channel(obj.javaClass).fireEvent(obj) + public ManagedProcess getProcess() { + return process; } - } - -val EVENT_BUS = EventBus() \ No newline at end of file diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/LoadedOneVersionEvent.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/LoadedOneVersionEvent.java new file mode 100644 index 000000000..bafca608a --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/LoadedOneVersionEvent.java @@ -0,0 +1,44 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import java.util.EventObject; + +/** + * This event gets fired when a minecraft version has been loaded. + *
+ * This event is fired on the {@link org.jackhuang.hmcl.event.EVENT_BUS} + * + * @param source {@link org.jackhuang.hmcl.game.GameRepository} + * @param version the version id. + * + * @author huangyuhui + */ +public final class LoadedOneVersionEvent extends EventObject { + + private final String version; + + public LoadedOneVersionEvent(Object source, String version) { + super(source); + this.version = version; + } + + public String getVersion() { + return version; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessExitedAbnormallyEvent.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessExitedAbnormallyEvent.java new file mode 100644 index 000000000..e0bb416e2 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessExitedAbnormallyEvent.java @@ -0,0 +1,44 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import java.util.EventObject; +import org.jackhuang.hmcl.util.ManagedProcess; + +/** + * This event gets fired when a JavaProcess exited abnormally and the exit code is not zero. + *

+ * This event is fired on the {@link org.jackhuang.hmcl.event.EVENT_BUS} + * + * @param source {@link org.jackhuang.hmcl.launch.ExitWaiter} + * @param value The process that exited abnormally. + * @author huangyuhui + */ +public final class ProcessExitedAbnormallyEvent extends EventObject { + + private final ManagedProcess process; + + public ProcessExitedAbnormallyEvent(Object source, ManagedProcess process) { + super(source); + this.process = process; + } + + public ManagedProcess getProcess() { + return process; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessStoppedEvent.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessStoppedEvent.java new file mode 100644 index 000000000..1e7fbf277 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/ProcessStoppedEvent.java @@ -0,0 +1,44 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import java.util.EventObject; +import org.jackhuang.hmcl.util.ManagedProcess; + +/** + * This event gets fired when minecraft process exited successfully and the exit code is 0. + *
+ * This event is fired on the {@link org.jackhuang.hmcl.event.EVENT_BUS} + * + * @param source {@link org.jackhuang.hmcl.launch.ExitWaiter} + * @param value minecraft process + * @author huangyuhui + */ +public class ProcessStoppedEvent extends EventObject { + + private final ManagedProcess process; + + public ProcessStoppedEvent(Object source, ManagedProcess process) { + super(source); + this.process = process; + } + + public ManagedProcess getProcess() { + return process; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshedVersionsEvent.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshedVersionsEvent.java new file mode 100644 index 000000000..b6acba78c --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshedVersionsEvent.java @@ -0,0 +1,37 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import java.util.EventObject; + +/** + * This event gets fired when all the versions in .minecraft folder are loaded. + *
+ * This event is fired on the {@link org.jackhuang.hmcl.api.HMCLApi#EVENT_BUS} + * + * @param source {@link org.jackhuang.hmcl.game.GameRepository] + * + * @author huangyuhui + */ +public final class RefreshedVersionsEvent extends EventObject { + + public RefreshedVersionsEvent(Object source) { + super(source); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshingVersionsEvent.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshingVersionsEvent.java new file mode 100644 index 000000000..b46555644 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/event/RefreshingVersionsEvent.java @@ -0,0 +1,37 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import java.util.EventObject; + +/** + * This event gets fired when loading versions in a .minecraft folder. + *
+ * This event is fired on the {@link org.jackhuang.hmcl.event.EVENT_BUS} + * + * @param source {@link org.jackhuang.hmcl.game.GameRepository} + * + * @author huangyuhui + */ +public final class RefreshingVersionsEvent extends EventObject { + + public RefreshingVersionsEvent(Object source) { + super(source); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Argument.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Argument.java new file mode 100644 index 000000000..ab119fbe3 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Argument.java @@ -0,0 +1,74 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public interface Argument extends Cloneable { + + /** + * Parse this argument in form: ${key name} or simply a string. + * + * @param keys the parse map + * @param features the map that contains some features such as 'is_demo_user', 'has_custom_resolution' + * @return parsed argument element, empty if this argument is ignored and will not be added. + */ + List toString(Map keys, Map features); + + public static class Serializer implements JsonDeserializer, JsonSerializer { + + public static final Serializer INSTANCE = new Serializer(); + + private Serializer() { + } + + @Override + public Argument deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonPrimitive()) + return new StringArgument(json.getAsString()); + else + return context.deserialize(json, RuledArgument.class); + } + + @Override + public JsonElement serialize(Argument src, Type typeOfSrc, JsonSerializationContext context) { + if (src instanceof StringArgument) + return new JsonPrimitive(((StringArgument) src).getArgument()); + else if (src instanceof RuledArgument) + return context.serialize(src, RuledArgument.class); + else + throw new AssertionError("Unrecognized argument type: " + src); + } + + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java new file mode 100644 index 000000000..50fc7be22 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java @@ -0,0 +1,112 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.annotations.SerializedName; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.OperatingSystem; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class Arguments { + + @SerializedName("game") + private final List game; + @SerializedName("jvm") + private final List jvm; + + public Arguments() { + this(null, null); + } + + public Arguments(List game, List jvm) { + this.game = game; + this.jvm = jvm; + } + + public List getGame() { + return game == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(game); + } + + public List getJvm() { + return jvm == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(jvm); + } + + public static Arguments addGameArguments(Arguments arguments, String... gameArguments) { + return addGameArguments(arguments, Arrays.asList(gameArguments)); + } + + public static Arguments addGameArguments(Arguments arguments, List gameArguments) { + List list = gameArguments.stream().map(StringArgument::new).collect(Collectors.toList()); + if (arguments == null) + return new Arguments(list, null); + else + return new Arguments(Lang.merge(arguments.getGame(), list), arguments.getJvm()); + } + + public static Arguments merge(Arguments a, Arguments b) { + if (a == null) + return b; + else if (b == null) + return a; + else + return new Arguments(Lang.merge(a.game, b.game), Lang.merge(a.jvm, b.jvm)); + } + + public static List parseStringArguments(List arguments, Map keys) { + return arguments.stream().map(str -> keys.getOrDefault(str, str)).collect(Collectors.toList()); + } + + public static List parseArguments(List arguments, Map keys) { + return parseArguments(arguments, keys, Collections.EMPTY_MAP); + } + + public static List parseArguments(List arguments, Map keys, Map features) { + return arguments.stream().flatMap(arg -> arg.toString(keys, features).stream()).collect(Collectors.toList()); + } + + public static final List DEFAULT_JVM_ARGUMENTS; + public static final List DEFAULT_GAME_ARGUMENTS; + + static { + List jvm = new LinkedList<>(); + jvm.add(new RuledArgument(Collections.singletonList(new CompatibilityRule(CompatibilityRule.Action.ALLOW, new OSRestriction(OperatingSystem.OSX))), Collections.singletonList("-XstartOnFirstThread"))); + jvm.add(new RuledArgument(Collections.singletonList(new CompatibilityRule(CompatibilityRule.Action.ALLOW, new OSRestriction(OperatingSystem.WINDOWS))), Collections.singletonList("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"))); + jvm.add(new RuledArgument(Collections.singletonList(new CompatibilityRule(CompatibilityRule.Action.ALLOW, new OSRestriction(OperatingSystem.WINDOWS, "^10\\."))), Arrays.asList("-Dos.name=Windows 10", "-Dos.version=10.0"))); + jvm.add(new StringArgument("-Djava.library.path=${natives_directory}")); + jvm.add(new StringArgument("-Dminecraft.launcher.brand=${launcher_name}")); + jvm.add(new StringArgument("-Dminecraft.launcher.version=${launcher_version}")); + jvm.add(new StringArgument("-cp")); + jvm.add(new StringArgument("${classpath}")); + DEFAULT_JVM_ARGUMENTS = Collections.unmodifiableList(jvm); + + List game = new LinkedList<>(); + game.add(new RuledArgument(Collections.singletonList(new CompatibilityRule(CompatibilityRule.Action.ALLOW, null, Collections.singletonMap("has_custom_resolution", true))), Arrays.asList("--width", "${resolution_width}", "--height", "${resolution_height}"))); + DEFAULT_GAME_ARGUMENTS = Collections.unmodifiableList(game); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetIndex.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndex.java similarity index 50% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetIndex.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndex.java index ff54da1c7..dbc30de9d 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetIndex.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndex.java @@ -15,19 +15,40 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.game +package org.jackhuang.hmcl.game; -import com.google.gson.annotations.SerializedName -import java.util.* +import com.google.gson.annotations.SerializedName; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; -class AssetIndex @JvmOverloads constructor( - @SerializedName("virtual") - val virtual: Boolean = false, - objects: Map = emptyMap() -) { - val objects: Map - get() = Collections.unmodifiableMap(objectsImpl) +/** + * + * @author huangyuhui + */ +public final class AssetIndex { + + @SerializedName("virtual") + private final boolean virtual; @SerializedName("objects") - private val objectsImpl: MutableMap = HashMap(objects) -} \ No newline at end of file + private final Map objects; + + public AssetIndex() { + this(false, Collections.EMPTY_MAP); + } + + public AssetIndex(boolean virtual, Map objects) { + this.virtual = virtual; + this.objects = new HashMap<>(objects); + } + + public boolean isVirtual() { + return virtual; + } + + public Map getObjects() { + return Collections.unmodifiableMap(objects); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndexInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndexInfo.java new file mode 100644 index 000000000..7e6149f9f --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetIndexInfo.java @@ -0,0 +1,55 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public class AssetIndexInfo extends IdDownloadInfo { + + private final long totalSize; + + public AssetIndexInfo() { + this("", ""); + } + + public AssetIndexInfo(String id, String url) { + this(id, url, null); + } + + public AssetIndexInfo(String id, String url, String sha1) { + this(id, url, sha1, 0); + } + + public AssetIndexInfo(String id, String url, String sha1, int size) { + this(id, url, sha1, size, 0); + } + + public AssetIndexInfo(String id, String url, String sha1, int size, long totalSize) { + super(id, url, sha1, size); + this.totalSize = totalSize; + } + + public long getTotalSize() { + return totalSize; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetObject.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetObject.java new file mode 100644 index 000000000..c5ec93e99 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/AssetObject.java @@ -0,0 +1,59 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.JsonParseException; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +public final class AssetObject implements Validation { + + private final String hash; + private final long size; + + public AssetObject() { + this("", 0); + } + + public AssetObject(String hash, long size) { + this.hash = hash; + this.size = size; + } + + public String getHash() { + return hash; + } + + public long getSize() { + return size; + } + + public String getLocation() { + return hash.substring(0, 2) + "/" + hash; + } + + @Override + public void validate() throws JsonParseException { + if (StringUtils.isBlank(hash)) + throw new IllegalStateException("AssetObject hash cannot be blank."); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/CircleDependencyException.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CircleDependencyException.java similarity index 70% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/CircleDependencyException.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/game/CircleDependencyException.java index cc1a863c3..43d641a4c 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/CircleDependencyException.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CircleDependencyException.java @@ -15,14 +15,24 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.game +package org.jackhuang.hmcl.game; /** * What's circle dependency? * When C inherits from B, and B inherits from something else, and finally inherits from C again. + * + * @author huangyuhui */ -class CircleDependencyException : GameException { - constructor() : super() {} - constructor(message: String) : super(message) {} - constructor(message: String, cause: Throwable) : super(message, cause) {} -} \ No newline at end of file +public final class CircleDependencyException extends GameException { + + public CircleDependencyException() { + } + + public CircleDependencyException(String message) { + super(message); + } + + public CircleDependencyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModpackManager.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicLibrary.java similarity index 88% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModpackManager.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicLibrary.java index a43950465..30e2c6290 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModpackManager.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicLibrary.java @@ -15,8 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.mod +package org.jackhuang.hmcl.game; -object ModpackManager { +/** + * + * @author huang + */ +public class ClassicLibrary { -} \ No newline at end of file +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java new file mode 100644 index 000000000..e08fa12b0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java @@ -0,0 +1,54 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import java.io.File; +import java.util.Arrays; +import java.util.Date; + +/** + * The Minecraft version for 1.5.x and earlier. + * + * @author huangyuhui + */ +public class ClassicVersion extends Version { + + public ClassicVersion() { + super("Classic", "${auth_player_name} ${auth_session} --workDir ${game_directory}", + null, "net.minecraft.client.Minecraft", null, null, null, null, + Arrays.asList(new ClassicLibrary("lwjgl"), new ClassicLibrary("jinput"), new ClassicLibrary("lwjgl_util")), + null, null, null, ReleaseType.UNKNOWN, new Date(), new Date(), 0); + } + + private static class ClassicLibrary extends Library { + + public ClassicLibrary(String name) { + super("", "", "", null, null, + new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null), + false, null, null, null); + } + } + + public static boolean hasClassicVersion(File baseDirectory) { + File bin = new File(baseDirectory, "bin"); + return bin.exists() + && new File(bin, "lwjgl.jar").exists() + && new File(bin, "jinput.jar").exists() + && new File(bin, "lwjgl_util.jar").exists(); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CompatibilityRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CompatibilityRule.java new file mode 100644 index 000000000..043356af3 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CompatibilityRule.java @@ -0,0 +1,85 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class CompatibilityRule { + + private final Action action; + private final OSRestriction os; + private final Map features; + + public CompatibilityRule() { + this(Action.ALLOW, null); + } + + public CompatibilityRule(Action action, OSRestriction os) { + this(action, os, null); + } + + public CompatibilityRule(Action action, OSRestriction os, Map features) { + this.action = action; + this.os = os; + this.features = features; + } + + public Action getAppliedAction(Map supportedFeatures) { + if (os != null && !os.allow()) + return null; + + if (features != null) + for (Map.Entry entry : features.entrySet()) + if (!Objects.equals(supportedFeatures.get(entry.getKey()), entry.getValue())) + return null; + + return action; + } + + public static boolean appliesToCurrentEnvironment(Collection rules) { + return appliesToCurrentEnvironment(rules, Collections.EMPTY_MAP); + } + + public static boolean appliesToCurrentEnvironment(Collection rules, Map features) { + if (rules == null) + return true; + + Action action = Action.DISALLOW; + for (CompatibilityRule rule : rules) { + Action thisAction = rule.getAppliedAction(features); + if (thisAction != null) + action = thisAction; + } + + return action == Action.ALLOW; + } + + public enum Action { + ALLOW, + DISALLOW + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java new file mode 100644 index 000000000..f2700f2dc --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -0,0 +1,311 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.JsonParseException; +import com.google.gson.JsonSyntaxException; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.logging.Level; +import org.jackhuang.hmcl.event.EventBus; +import org.jackhuang.hmcl.event.LoadedOneVersionEvent; +import org.jackhuang.hmcl.event.RefreshedVersionsEvent; +import org.jackhuang.hmcl.event.RefreshingVersionsEvent; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Logging; + +/** + * An implementation of classic Minecraft game repository. + * + * @author huangyuhui + */ +public class DefaultGameRepository implements GameRepository { + + private File baseDirectory; + protected final Map versions = new TreeMap<>(); + protected boolean loaded = false; + + public DefaultGameRepository(File baseDirectory) { + this.baseDirectory = baseDirectory; + } + + public File getBaseDirectory() { + return baseDirectory; + } + + public void setBaseDirectory(File baseDirectory) { + this.baseDirectory = baseDirectory; + } + + @Override + public boolean hasVersion(String id) { + return versions.containsKey(id); + } + + @Override + public Version getVersion(String id) { + if (!hasVersion(id)) + throw new VersionNotFoundException("Version '" + id + "' does not exist."); + return versions.get(id); + } + + @Override + public int getVersionCount() { + return versions.size(); + } + + @Override + public Collection getVersions() { + return versions.values(); + } + + @Override + public File getLibraryFile(Version version, Library lib) { + return new File(getBaseDirectory(), "libraries/" + lib.getPath()); + } + + @Override + public File getRunDirectory(String id) { + return getBaseDirectory(); + } + + @Override + public File getVersionJar(Version version) { + Version v = version.resolve(this); + String id = Lang.nonNull(v.getJar(), v.getId()); + return new File(getVersionRoot(id), id + ".jar"); + } + + @Override + public File getNativeDirectory(String id) { + return new File(getVersionRoot(id), id + "-natives"); + } + + @Override + public File getVersionRoot(String id) { + return new File(getBaseDirectory(), "versions/" + id); + } + + public File getVersionJson(String id) { + return new File(getVersionRoot(id), id + ".json"); + } + + public Version readVersionJson(String id) throws IOException, JsonSyntaxException { + return readVersionJson(getVersionJson(id)); + } + + public Version readVersionJson(File file) throws IOException, JsonSyntaxException { + return Constants.GSON.fromJson(FileUtils.readText(file), Version.class); + } + + @Override + public boolean renameVersion(String from, String to) { + try { + Version fromVersion = getVersion(from); + File fromDir = getVersionRoot(from); + File toDir = getVersionRoot(to); + if (!fromDir.renameTo(toDir)) + return false; + + File toJson = new File(toDir, to + ".json"); + File toJar = new File(toDir, to + ".jar"); + + if (!new File(toDir, from + ".json").renameTo(toJson) + || !new File(toDir, from + ".jar").renameTo(toJar)) { + // recovery + toJson.renameTo(new File(toDir, from + ".json")); + toJar.renameTo(new File(toDir, from + ".jar")); + toDir.renameTo(fromDir); + return false; + } + + FileUtils.writeText(toJson, Constants.GSON.toJson(fromVersion.setId(to))); + return true; + } catch (IOException | JsonSyntaxException | VersionNotFoundException e) { + return false; + } + } + + public boolean removeVersionFromDisk(String id) { + File file = getVersionRoot(id); + if (!file.exists()) + return true; + versions.remove(id); + return FileUtils.deleteDirectoryQuietly(file); + } + + protected void refreshVersionsImpl() { + versions.clear(); + + if (ClassicVersion.hasClassicVersion(getBaseDirectory())) { + Version version = new ClassicVersion(); + versions.put(version.getId(), version); + } + + File[] files = new File(getBaseDirectory(), "versions").listFiles(); + if (files != null) + for (File dir : files) + if (dir.isDirectory()) { + String id = dir.getName(); + File json = new File(dir, id + ".json"); + + // If user renamed the json file by mistake or created the json file in a wrong name, + // we will find the only json and rename it to correct name. + if (!json.exists()) { + List jsons = FileUtils.listFilesByExtension(dir, "json"); + if (jsons.size() == 1) + jsons.get(0).renameTo(json); + } + + Version version; + try { + version = Objects.requireNonNull(readVersionJson(json)); + } catch (Exception e) { + // JsonSyntaxException or IOException or NullPointerException(!!) + // TODO: auto making up for the missing json + // TODO: and even asking for removing the redundant version folder. + continue; + } + + if (!id.equals(version.getId())) { + version = version.setId(id); + try { + FileUtils.writeText(json, Constants.GSON.toJson(version)); + } catch (Exception e) { + Logging.LOG.log(Level.WARNING, "Ignoring version {0} because wrong id {1} is set and cannot correct it.", new Object[] { id, version.getId() }); + continue; + } + } + + versions.put(id, version); + EventBus.EVENT_BUS.fireEvent(new LoadedOneVersionEvent(this, id)); + } + + loaded = true; + } + + @Override + public final void refreshVersions() { + EventBus.EVENT_BUS.fireEvent(new RefreshingVersionsEvent(this)); + refreshVersionsImpl(); + EventBus.EVENT_BUS.fireEvent(new RefreshedVersionsEvent(this)); + } + + @Override + public AssetIndex getAssetIndex(String version, String assetId) throws IOException { + try { + return Objects.requireNonNull(Constants.GSON.fromJson(FileUtils.readText(getIndexFile(version, assetId)), AssetIndex.class)); + } catch (JsonParseException | NullPointerException e) { + throw new IOException("Asset index file malformed", e); + } + } + + @Override + public File getActualAssetDirectory(String version, String assetId) { + try { + return reconstructAssets(version, assetId); + } catch (IOException | JsonParseException e) { + Logging.LOG.log(Level.SEVERE, "Unable to reconstruct asset directory", e); + return getAssetDirectory(version, assetId); + } + } + + @Override + public File getAssetDirectory(String version, String assetId) { + return new File(getBaseDirectory(), "assets"); + } + + @Override + public File getAssetObject(String version, String assetId, String name) throws IOException { + try { + return getAssetObject(version, assetId, getAssetIndex(version, assetId).getObjects().get(name)); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException("Unrecognized asset object " + name + " in asset " + assetId + " of version " + version, e); + } + } + + @Override + public File getAssetObject(String version, String assetId, AssetObject obj) { + return getAssetObject(version, getAssetDirectory(version, assetId), obj); + } + + public File getAssetObject(String version, File assetDir, AssetObject obj) { + return new File(assetDir, "objects/" + obj.getLocation()); + } + + @Override + public File getIndexFile(String version, String assetId) { + return new File(getAssetDirectory(version, assetId), "indexes/" + assetId + ".json"); + } + + @Override + public File getLoggingObject(String version, String assetId, LoggingInfo loggingInfo) { + return new File(getAssetDirectory(version, assetId), "log_configs/" + loggingInfo.getFile().getId()); + } + + protected File reconstructAssets(String version, String assetId) throws IOException, JsonParseException { + File assetsDir = getAssetDirectory(version, assetId); + File indexFile = getIndexFile(version, assetId); + File virtualRoot = new File(new File(assetsDir, "virtual"), assetId); + + if (!indexFile.isFile()) + return assetsDir; + + String assetIndexContent = FileUtils.readText(indexFile); + AssetIndex index = (AssetIndex) Constants.GSON.fromJson(assetIndexContent, AssetIndex.class); + + if (index == null) + return assetsDir; + + if (index.isVirtual()) { + int cnt = 0; + int tot = index.getObjects().entrySet().size(); + for (Map.Entry entry : index.getObjects().entrySet()) { + File target = new File(virtualRoot, entry.getKey()); + File original = getAssetObject(version, assetsDir, entry.getValue()); + if (original.exists()) { + cnt++; + if (!target.isFile()) + FileUtils.copyFile(original, target); + } + } + + // If the scale new format existent file is lower then 0.1, use the old format. + if (cnt * 10 < tot) + return assetsDir; + else + return virtualRoot; + } + + return assetsDir; + } + + public boolean isLoaded() { + return loaded; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DownloadInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DownloadInfo.java new file mode 100644 index 000000000..0eddcf612 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DownloadInfo.java @@ -0,0 +1,75 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +@Immutable +public class DownloadInfo implements Validation { + + @SerializedName("url") + private final String url; + @SerializedName("sha1") + private final String sha1; + @SerializedName("size") + private final int size; + + public DownloadInfo() { + this(""); + } + + public DownloadInfo(String url) { + this(url, null); + } + + public DownloadInfo(String url, String sha1) { + this(url, sha1, 0); + } + + public DownloadInfo(String url, String sha1, int size) { + this.url = url; + this.sha1 = sha1; + this.size = size; + } + + public String getUrl() { + return url; + } + + public String getSha1() { + return sha1; + } + + public int getSize() { + return size; + } + + @Override + public void validate() throws JsonParseException { + if (StringUtils.isBlank(url)) + throw new JsonParseException("DownloadInfo url can not be null"); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Property.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DownloadType.java similarity index 84% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Property.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/game/DownloadType.java index ebdbeb777..3a3931a90 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Property.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DownloadType.java @@ -15,6 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.auth.yggdrasil +package org.jackhuang.hmcl.game; -data class Property(val name: String, val value: String) \ No newline at end of file +/** + * + * @author huangyuhui + */ +public enum DownloadType { + CLIENT, + SERVER, + WINDOWS_SERVER +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ExtractRules.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ExtractRules.java new file mode 100644 index 000000000..7aa3e7526 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ExtractRules.java @@ -0,0 +1,50 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author huangyuhui + */ +public final class ExtractRules { + + public static final ExtractRules EMPTY = new ExtractRules(); + + private final List exclude; + + public ExtractRules() { + this.exclude = Collections.EMPTY_LIST; + } + + public ExtractRules(List exclude) { + this.exclude = new LinkedList<>(exclude); + } + + public List getExclude() { + return Collections.unmodifiableList(exclude); + } + + public boolean shouldExtract(String path) { + return exclude.stream().noneMatch(it -> path.startsWith(it)); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameException.java new file mode 100644 index 000000000..f97c25899 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameException.java @@ -0,0 +1,40 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +/** + * + * @author huangyuhui + */ +public class GameException extends Exception { + + public GameException() { + } + + public GameException(String message) { + super(message); + } + + public GameException(String message, Throwable cause) { + super(message, cause); + } + + public GameException(Throwable cause) { + super(cause); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameRepository.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java similarity index 80% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameRepository.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java index 2daeda6e0..e3c0aaedd 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameRepository.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameRepository.java @@ -15,42 +15,50 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.game +package org.jackhuang.hmcl.game; -import java.io.File -import java.io.IOException +import java.io.File; +import java.io.IOException; +import java.util.Collection; /** * Supports operations on versioning. * - * Note that game repository will not do any tasks that connect to Internet, if do, see [org.jackhuang.hmcl.download.DependencyManager] + * Note that game repository will not do any tasks that connect to Internet, if do, see {@link org.jackhuang.hmcl.download.DependencyManager} + * + * @author huangyuhui */ -interface GameRepository : VersionProvider { +public interface GameRepository extends VersionProvider { /** * Does the version of id exist? + * * @param id the id of version * @return true if the version exists */ - override fun hasVersion(id: String): Boolean + @Override + boolean hasVersion(String id); /** * Get the version + * * @param id the id of version * @return the version you want */ - override fun getVersion(id: String): Version + @Override + Version getVersion(String id); /** * How many version are there? */ - fun getVersionCount(): Int + int getVersionCount(); /** * Gets the collection of versions + * * @return the collection of versions */ - fun getVersions(): Collection + Collection getVersions(); /** * Load version list. @@ -59,20 +67,21 @@ interface GameRepository : VersionProvider { * A time-costly operation. * You'd better execute this method in a new thread. */ - fun refreshVersions() + void refreshVersions(); /** * Gets the root folder of specific version. * The root folders the versions must be unique. * For example, .minecraft/versions//. */ - fun getVersionRoot(id: String): File + File getVersionRoot(String id); /** * Gets the current running directory of the given version for game. + * * @param id the version id */ - fun getRunDirectory(id: String): File + File getRunDirectory(String id); /** * Get the library file in disk. @@ -82,7 +91,7 @@ interface GameRepository : VersionProvider { * @param lib the library, [Version.libraries] * @return the library file */ - fun getLibraryFile(version: Version, lib: Library): File + File getLibraryFile(Version version, Library lib); /** * Get the directory that native libraries will be unzipped to. @@ -94,7 +103,7 @@ interface GameRepository : VersionProvider { * @param id version id * @return the native directory */ - fun getNativeDirectory(id: String): File + File getNativeDirectory(String id); /** * Get minecraft jar @@ -102,7 +111,7 @@ interface GameRepository : VersionProvider { * @param version resolvedVersion * @return the minecraft jar */ - fun getVersionJar(version: Version): File + File getVersionJar(Version version); /** * Get minecraft jar @@ -110,7 +119,9 @@ interface GameRepository : VersionProvider { * @param version version id * @return the minecraft jar */ - fun getVersionJar(version: String): File = getVersionJar(getVersion(version).resolve(this)) + default File getVersionJar(String version) { + return getVersionJar(getVersion(version).resolve(this)); + } /** * Rename given version to new name. @@ -121,7 +132,7 @@ interface GameRepository : VersionProvider { * @throws java.io.IOException if I/O operation fails. * @return true if the operation is done successfully. */ - fun renameVersion(from: String, to: String): Boolean + boolean renameVersion(String from, String to); /** * Get actual asset directory. @@ -133,7 +144,7 @@ interface GameRepository : VersionProvider { * @throws java.io.IOException if I/O operation fails. * @return the actual asset directory */ - fun getActualAssetDirectory(version: String, assetId: String): File + File getActualAssetDirectory(String version, String assetId); /** * Get the asset directory according to the asset id. @@ -142,7 +153,7 @@ interface GameRepository : VersionProvider { * @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id] * @return the asset directory */ - fun getAssetDirectory(version: String, assetId: String): File + File getAssetDirectory(String version, String assetId); /** * Get the file that given asset object refers to @@ -153,8 +164,7 @@ interface GameRepository : VersionProvider { * @throws java.io.IOException if I/O operation fails. * @return the file that given asset object refers to */ - @Throws(IOException::class) - fun getAssetObject(version: String, assetId: String, name: String): File + File getAssetObject(String version, String assetId, String name) throws IOException; /** * Get the file that given asset object refers to @@ -164,7 +174,7 @@ interface GameRepository : VersionProvider { * @param obj the asset object, you can find it in [AssetIndex.objects] * @return the file that given asset object refers to */ - fun getAssetObject(version: String, assetId: String, obj: AssetObject): File + File getAssetObject(String version, String assetId, AssetObject obj); /** * Get asset index that assetId represents @@ -173,7 +183,7 @@ interface GameRepository : VersionProvider { * @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id] * @return the asset index */ - fun getAssetIndex(version: String, assetId: String): AssetIndex + AssetIndex getAssetIndex(String version, String assetId) throws IOException; /** * Get the asset_index.json which includes asset objects information. @@ -181,7 +191,7 @@ interface GameRepository : VersionProvider { * @param version the id of specific version that is relevant to [assetId] * @param assetId the asset id, you can find it in [AssetIndexInfo.id] [Version.actualAssetIndex.id] */ - fun getIndexFile(version: String, assetId: String): File + File getIndexFile(String version, String assetId); /** * Get logging object @@ -191,6 +201,6 @@ interface GameRepository : VersionProvider { * @param loggingInfo the logging info * @return the file that loggingInfo refers to */ - fun getLoggingObject(version: String, assetId: String, loggingInfo: LoggingInfo): File + File getLoggingObject(String version, String assetId, LoggingInfo loggingInfo); -} \ No newline at end of file +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java new file mode 100644 index 000000000..725a5ff4e --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java @@ -0,0 +1,129 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.jackhuang.hmcl.util.Charsets; +import org.jackhuang.hmcl.util.IOUtils; + +import java.io.File; +import java.io.IOException; + +/** + * @author huangyuhui + */ +public final class GameVersion { + private static int lessThan32(byte[] b, int x) { + for (; x < b.length; x++) + if (b[x] < 32) + return x; + return -1; + } + + public static int matchArray(byte[] a, byte[] b) { + for (int i = 0; i < a.length - b.length; i++) { + int j = 1; + for (int k = 0; k < b.length; k++) { + if (b[k] == a[(i + k)]) + continue; + j = 0; + break; + } + if (j != 0) + return i; + } + return -1; + } + + private static String getVersionOfOldMinecraft(ZipFile file, ZipArchiveEntry entry) throws IOException { + byte[] tmp = IOUtils.readFullyAsByteArray(file.getInputStream(entry)); + + byte[] bytes = "Minecraft Minecraft ".getBytes(Charsets.US_ASCII); + int j = matchArray(tmp, bytes); + if (j < 0) + return null; + int i = j + bytes.length; + + if ((j = lessThan32(tmp, i)) < 0) + return null; + + return new String(tmp, i, j - i, Charsets.US_ASCII); + } + + private static String getVersionOfNewMinecraft(ZipFile file, ZipArchiveEntry entry) throws IOException { + byte[] tmp = IOUtils.readFullyAsByteArray(file.getInputStream(entry)); + + byte[] str = "-server.txt".getBytes(Charsets.US_ASCII); + int j = matchArray(tmp, str); + if (j < 0) return null; + int i = j + str.length; + i += 11; + j = lessThan32(tmp, i); + if (j < 0) return null; + String result = new String(tmp, i, j - i, Charsets.US_ASCII); + + char ch = result.charAt(0); + // 1.8.1+ + if (ch < '0' || ch > '9') { + str = "Can't keep up! Did the system time change, or is the server overloaded?".getBytes(Charsets.US_ASCII); + j = matchArray(tmp, str); + if (j < 0) return null; + i = -1; + while (j > 0) { + if (tmp[j] >= 48 && tmp[j] <= 57) { + i = j; + break; + } + j--; + } + if (i == -1) return null; + int k = i; + if (tmp[i + 1] >= (int) 'a' && tmp[i + 1] <= (int) 'z') + i++; + while (tmp[k] >= 48 && tmp[k] <= 57 || tmp[k] == (int) '-' || tmp[k] == (int) '.' || tmp[k] >= 97 && tmp[k] <= (int) 'z') + k--; + k++; + return new String(tmp, k, i - k + 1, Charsets.US_ASCII); + } + return result; + } + + public static String minecraftVersion(File file) { + if (file == null || !file.exists() || !file.isFile() || !file.canRead()) + return null; + + ZipFile f = null; + try { + f = new ZipFile(file); + ZipArchiveEntry minecraft = f + .getEntry("net/minecraft/client/Minecraft.class"); + if (minecraft != null) + return getVersionOfOldMinecraft(f, minecraft); + ZipArchiveEntry main = f.getEntry("net/minecraft/client/main/Main.class"); + ZipArchiveEntry minecraftserver = f.getEntry("net/minecraft/server/MinecraftServer.class"); + if ((main != null) && (minecraftserver != null)) + return getVersionOfNewMinecraft(f, minecraftserver); + return null; + } catch (IOException e) { + return null; + } finally { + IOUtils.closeQuietly(f); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/IdDownloadInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/IdDownloadInfo.java new file mode 100644 index 000000000..d09fa3c22 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/IdDownloadInfo.java @@ -0,0 +1,64 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.StringUtils; + +/** + * + * @author huangyuhui + */ +@Immutable +public class IdDownloadInfo extends DownloadInfo { + + @SerializedName("id") + private final String id; + + public IdDownloadInfo() { + this("", ""); + } + + public IdDownloadInfo(String id, String url) { + this(id, url, null); + } + + public IdDownloadInfo(String id, String url, String sha1) { + this(id, url, sha1, 0); + } + + public IdDownloadInfo(String id, String url, String sha1, int size) { + super(url, sha1, size); + this.id = id; + } + + public String getId() { + return id; + } + + @Override + public void validate() throws JsonParseException { + super.validate(); + + if (StringUtils.isBlank(id)) + throw new JsonParseException("IdDownloadInfo id can not be null"); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java new file mode 100644 index 000000000..9c454bd3c --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java @@ -0,0 +1,303 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import java.io.File; +import java.io.Serializable; +import org.jackhuang.hmcl.util.JavaVersion; + +/** + * + * @author huangyuhui + */ +public class LaunchOptions implements Serializable { + + private File gameDir; + private JavaVersion java; + private String versionName; + private String profileName; + private String minecraftArgs; + private String javaArgs; + private Integer minMemory; + private Integer maxMemory; + private Integer metaspace; + private Integer width; + private Integer height; + private boolean fullscreen; + private String serverIp; + private String wrapper; + private String proxyHost; + private String proxyPort; + private String proxyUser; + private String proxyPass; + private boolean noGeneratedJVMArgs; + private String precalledCommand; + + /** + * The game directory + */ + public File getGameDir() { + return gameDir; + } + + /** + * The Java Environment that Minecraft runs on. + */ + public JavaVersion getJava() { + return java; + } + + /** + * Will shown in the left bottom corner of the main menu of Minecraft. + * null if use the id of launch version. + */ + public String getVersionName() { + return versionName; + } + + /** + * Don't know what the hell this is. + */ + public String getProfileName() { + return profileName; + } + + /** + * User custom additional minecraft command line arguments. + */ + public String getMinecraftArgs() { + return minecraftArgs; + } + + /** + * User custom additional java virtual machine command line arguments. + */ + public String getJavaArgs() { + return javaArgs; + } + + /** + * The minimum memory that the JVM can allocate. + */ + public Integer getMinMemory() { + return minMemory; + } + + /** + * The maximum memory that the JVM can allocate. + */ + public Integer getMaxMemory() { + return maxMemory; + } + + /** + * The maximum metaspace memory that the JVM can allocate. + * For Java 7 -XX:PermSize and Java 8 -XX:MetaspaceSize + * Containing class instances. + */ + public Integer getMetaspace() { + return metaspace; + } + + /** + * The initial game window width + */ + public Integer getWidth() { + return width; + } + + /** + * The initial game window height + */ + public Integer getHeight() { + return height; + } + + /** + * Is inital game window fullscreen. + */ + public boolean isFullscreen() { + return fullscreen; + } + + /** + * The server ip that will connect to when enter game main menu. + */ + public String getServerIp() { + return serverIp; + } + + /** + * i.e. optirun + */ + public String getWrapper() { + return wrapper; + } + + /** + * The host of the proxy address + */ + public String getProxyHost() { + return proxyHost; + } + + /** + * the port of the proxy address. + */ + public String getProxyPort() { + return proxyPort; + } + + /** + * The user name of the proxy, optional. + */ + public String getProxyUser() { + return proxyUser; + } + + /** + * The password of the proxy, optional + */ + public String getProxyPass() { + return proxyPass; + } + + /** + * Prevent game launcher from generating default JVM arguments like max memory. + */ + public boolean isNoGeneratedJVMArgs() { + return noGeneratedJVMArgs; + } + + /** + * Called command line before launching the game. + */ + public String getPrecalledCommand() { + return precalledCommand; + } + + public static class Builder { + + LaunchOptions options = new LaunchOptions(); + + public LaunchOptions create() { + return options; + } + + public Builder setGameDir(File gameDir) { + options.gameDir = gameDir; + return this; + } + + public Builder setJava(JavaVersion java) { + options.java = java; + return this; + } + + public Builder setVersionName(String versionName) { + options.versionName = versionName; + return this; + } + + public Builder setProfileName(String profileName) { + options.profileName = profileName; + return this; + } + + public Builder setMinecraftArgs(String minecraftArgs) { + options.minecraftArgs = minecraftArgs; + return this; + } + + public Builder setJavaArgs(String javaArgs) { + options.javaArgs = javaArgs; + return this; + } + + public Builder setMinMemory(Integer minMemory) { + options.minMemory = minMemory; + return this; + } + + public Builder setMaxMemory(Integer maxMemory) { + options.maxMemory = maxMemory; + return this; + } + + public Builder setMetaspace(Integer metaspace) { + options.metaspace = metaspace; + return this; + } + + public Builder setWidth(Integer width) { + options.width = width; + return this; + } + + public Builder setHeight(Integer height) { + options.height = height; + return this; + } + + public Builder setFullscreen(boolean fullscreen) { + options.fullscreen = fullscreen; + return this; + } + + public Builder setServerIp(String serverIp) { + options.serverIp = serverIp; + return this; + } + + public Builder setWrapper(String wrapper) { + options.wrapper = wrapper; + return this; + } + + public Builder setProxyHost(String proxyHost) { + options.proxyHost = proxyHost; + return this; + } + + public Builder setProxyPort(String proxyPort) { + options.proxyPort = proxyPort; + return this; + } + + public Builder setProxyUser(String proxyUser) { + options.proxyUser = proxyUser; + return this; + } + + public Builder setProxyPass(String proxyPass) { + options.proxyPass = proxyPass; + return this; + } + + public Builder setNoGeneratedJVMArgs(boolean noGeneratedJVMArgs) { + options.noGeneratedJVMArgs = noGeneratedJVMArgs; + return this; + } + + public Builder setPrecalledCommand(String precalledCommand) { + options.precalledCommand = precalledCommand; + return this; + } + + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibrariesDownloadInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibrariesDownloadInfo.java new file mode 100644 index 000000000..094ed9462 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibrariesDownloadInfo.java @@ -0,0 +1,52 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class LibrariesDownloadInfo { + + private final LibraryDownloadInfo artifact; + private final Map classifiers; + + public LibrariesDownloadInfo(LibraryDownloadInfo artifact) { + this(artifact, Collections.EMPTY_MAP); + } + + public LibrariesDownloadInfo(LibraryDownloadInfo artifact, Map classifiers) { + this.artifact = artifact; + this.classifiers = new HashMap<>(classifiers); + } + + public LibraryDownloadInfo getArtifact() { + return artifact; + } + + public Map getClassifiers() { + return classifiers == null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(classifiers); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java new file mode 100644 index 000000000..6885de35d --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java @@ -0,0 +1,203 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.OperatingSystem; +import org.jackhuang.hmcl.util.Platform; + +/** + * A class that describes a Minecraft dependency. + * + * @author huangyuhui + */ +public class Library { + + private final String groupId; + private final String artifactId; + private final String version; + private final String classifier; + private final String url; + private final LibrariesDownloadInfo downloads; + private final LibraryDownloadInfo download; + private final ExtractRules extract; + private final boolean lateload; + private final Map natives; + private final List rules; + + private final String path; + + public Library(String groupId, String artifactId, String version) { + this(groupId, artifactId, version, null, null, null); + } + + public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads) { + this(groupId, artifactId, version, classifier, url, downloads, false); + } + + public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload) { + this(groupId, artifactId, version, classifier, url, downloads, lateload, null, null, null); + } + + public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload, ExtractRules extract, Map natives, List rules) { + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; + if (classifier == null) + if (natives != null && natives.containsKey(OperatingSystem.CURRENT_OS)) + this.classifier = natives.get(OperatingSystem.CURRENT_OS).replace("${arch}", Platform.PLATFORM.getBit()); + else + this.classifier = null; + else + this.classifier = classifier; + this.url = url; + this.downloads = downloads; + this.extract = extract; + this.lateload = lateload; + this.natives = natives; + this.rules = rules; + + LibraryDownloadInfo temp = null; + if (downloads != null) + if (isNative()) + temp = downloads.getClassifiers().get(this.classifier); + else + temp = downloads.getArtifact(); + + if (temp != null && temp.getPath() != null) + path = temp.getPath(); + else + path = String.format("%s/%s/%s/%s-%s", groupId.replace(".", "/"), artifactId, version, artifactId, version) + + (this.classifier == null ? "" : "-" + this.classifier) + ".jar"; + + download = new LibraryDownloadInfo(path, + Lang.nonNull(Lang.nonNull(temp != null ? temp.getUrl() : null), Lang.nonNull(url, Constants.DEFAULT_LIBRARY_URL) + path), + temp != null ? temp.getSha1() : null, + temp != null ? temp.getSize() : 0 + ); + } + + public String getGroupId() { + return groupId; + } + + public String getArtifactId() { + return artifactId; + } + + public String getVersion() { + return version; + } + + public ExtractRules getExtract() { + return extract == null ? ExtractRules.EMPTY : extract; + } + + public boolean appliesToCurrentEnvironment() { + return CompatibilityRule.appliesToCurrentEnvironment(rules); + } + + public boolean isNative() { + return natives != null && appliesToCurrentEnvironment(); + } + + public String getPath() { + return path; + } + + public LibraryDownloadInfo getDownload() { + return download; + } + + public boolean isLateload() { + return lateload; + } + + @Override + public String toString() { + return "Library[" + groupId + ":" + artifactId + ":" + version + "]"; + } + + public static Library fromName(String name) { + return fromName(name, null, null, null, null, null); + } + + public static Library fromName(String name, String url, LibrariesDownloadInfo downloads, ExtractRules extract, Map natives, List rules) { + String[] arr = name.split(":", 4); + if (arr.length != 3 && arr.length != 4) + throw new IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version(:classifier)."); + + return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, false, extract, natives, rules); + } + + public static class Serializer implements JsonDeserializer, JsonSerializer { + + public static final Serializer INSTANCE = new Serializer(); + + private Serializer() { + } + + @Override + public Library deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + if (json == null || json == JsonNull.INSTANCE) + return null; + JsonObject jsonObject = json.getAsJsonObject(); + if (!jsonObject.has("name")) + throw new JsonParseException("Library name not found."); + return fromName( + jsonObject.get("name").getAsString(), + jsonObject.has("url") ? jsonObject.get("url").getAsString() : null, + context.deserialize(jsonObject.get("downloads"), LibrariesDownloadInfo.class), + context.deserialize(jsonObject.get("extract"), ExtractRules.class), + context.deserialize(jsonObject.get("natives"), new TypeToken>() { + }.getType()), + context.deserialize(jsonObject.get("rules"), new TypeToken>() { + }.getType())); + } + + @Override + public JsonElement serialize(Library src, Type type, JsonSerializationContext context) { + if (src == null) + return JsonNull.INSTANCE; + JsonObject obj = new JsonObject(); + obj.addProperty("name", src.groupId + ":" + src.artifactId + ":" + src.version); + obj.addProperty("url", src.url); + obj.add("downloads", context.serialize(src.downloads)); + obj.add("extract", context.serialize(src.extract)); + obj.add("natives", context.serialize(src.natives, new TypeToken>() { + }.getType())); + obj.add("rules", context.serialize(src.rules, new TypeToken>() { + }.getType())); + return obj; + } + + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibraryDownloadInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibraryDownloadInfo.java new file mode 100644 index 000000000..6c5ff93f3 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LibraryDownloadInfo.java @@ -0,0 +1,57 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public class LibraryDownloadInfo extends DownloadInfo { + + @SerializedName("path") + private final String path; + + public LibraryDownloadInfo() { + this(null); + } + + public LibraryDownloadInfo(String path) { + this(path, ""); + } + + public LibraryDownloadInfo(String path, String url) { + this(path, url, null); + } + + public LibraryDownloadInfo(String path, String url, String sha1) { + this(path, url, sha1, 0); + } + + public LibraryDownloadInfo(String path, String url, String sha1, int size) { + super(url, sha1, size); + this.path = path; + } + + public String getPath() { + return path; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LoggingInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LoggingInfo.java new file mode 100644 index 000000000..f11e9b570 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LoggingInfo.java @@ -0,0 +1,76 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +public final class LoggingInfo implements Validation { + + @SerializedName("file") + private final IdDownloadInfo file; + @SerializedName("argument") + private final String argument; + @SerializedName("type") + private final String type; + + public LoggingInfo() { + this(new IdDownloadInfo()); + } + + public LoggingInfo(IdDownloadInfo file) { + this(file, ""); + } + + public LoggingInfo(IdDownloadInfo file, String argument) { + this(file, argument, ""); + } + + public LoggingInfo(IdDownloadInfo file, String argument, String type) { + this.file = file; + this.argument = argument; + this.type = type; + } + + public IdDownloadInfo getFile() { + return file; + } + + public String getArgument() { + return argument; + } + + public String getType() { + return type; + } + + @Override + public void validate() throws JsonParseException { + file.validate(); + if (StringUtils.isBlank(argument)) + throw new JsonParseException("LoggingInfo.argument is empty."); + if (StringUtils.isBlank(type)) + throw new JsonParseException("LoggingInfo.type is empty."); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/OSRestriction.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/OSRestriction.java new file mode 100644 index 000000000..94b150618 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/OSRestriction.java @@ -0,0 +1,79 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import java.util.regex.Pattern; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.OperatingSystem; + +/** + * + * @author huangyuhui + */ +public final class OSRestriction { + + private final OperatingSystem name; + private final String version; + private final String arch; + + public OSRestriction() { + this(OperatingSystem.UNKNOWN); + } + + public OSRestriction(OperatingSystem name) { + this(name, null); + } + + public OSRestriction(OperatingSystem name, String version) { + this(name, version, null); + } + + public OSRestriction(OperatingSystem name, String version, String arch) { + this.name = name; + this.version = version; + this.arch = arch; + } + + public OperatingSystem getName() { + return name; + } + + public String getVersion() { + return version; + } + + public String getArch() { + return arch; + } + + public boolean allow() { + if (name != OperatingSystem.UNKNOWN && name != OperatingSystem.CURRENT_OS) + return false; + + if (version != null) + if (Lang.test(() -> !Pattern.compile(version).matcher(OperatingSystem.SYSTEM_VERSION).matches())) + return false; + + if (arch != null) + if (Lang.test(() -> !Pattern.compile(arch).matcher(OperatingSystem.SYSTEM_ARCHITECTURE).matches())) + return false; + + return true; + } + +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ReleaseType.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ReleaseType.java similarity index 72% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ReleaseType.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/game/ReleaseType.java index 9f74efc33..da17bc5af 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ReleaseType.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ReleaseType.java @@ -15,21 +15,28 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.game +package org.jackhuang.hmcl.game; -import com.google.gson.annotations.SerializedName - -enum class ReleaseType(val id: String) { - @SerializedName("release") +/** + * + * @author huangyuhui + */ +public enum ReleaseType { RELEASE("release"), - @SerializedName("snapshot") SNAPSHOT("snapshot"), - @SerializedName("modified") MODIFIED("modified"), - @SerializedName("old-beta") OLD_BETA("old-beta"), - @SerializedName("old-alpha") OLD_ALPHA("old-alpha"), - @SerializedName("unknown") - UNKNOWN("unknown") -} \ No newline at end of file + UNKNOWN("unknown"); + + private final String id; + + private ReleaseType(String id) { + this.id = id; + } + + public String getId() { + return id; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java new file mode 100644 index 000000000..f97a54c2b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java @@ -0,0 +1,110 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public class RuledArgument implements Argument { + + private final List rules; + private final List value; + + public RuledArgument() { + this(null, null); + } + + public RuledArgument(List rules, List args) { + this.rules = rules; + this.value = args; + } + + public List getRules() { + return Collections.unmodifiableList(rules); + } + + public List getValue() { + return Collections.unmodifiableList(value); + } + + @Override + public Object clone() { + return new RuledArgument( + rules == null ? null : new ArrayList<>(rules), + value == null ? null : new ArrayList<>(value) + ); + } + + @Override + public List toString(Map keys, Map features) { + if (CompatibilityRule.appliesToCurrentEnvironment(rules) && value != null) + return value.stream() + .map(StringArgument::new) + .map(str -> str.toString(keys, features).get(0)) + .collect(Collectors.toList()); + return Collections.EMPTY_LIST; + } + + public static class Serializer implements JsonSerializer, JsonDeserializer { + + public static final Serializer INSTANCE = new Serializer(); + + private Serializer() { + } + + @Override + public JsonElement serialize(RuledArgument src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject obj = new JsonObject(); + obj.add("rules", context.serialize(src.rules)); + obj.add("value", context.serialize(src.value)); + return obj; + } + + @Override + public RuledArgument deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject obj = json.getAsJsonObject(); + RuledArgument a = new RuledArgument( + context.deserialize(obj.get("rules"), new TypeToken>() { + }.getType()), + obj.get("value").isJsonPrimitive() + ? Collections.singletonList(obj.get("value").getAsString()) + : context.deserialize(obj.get("value"), new TypeToken>() { + }.getType())); + return a; + } + + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/SimpleVersionProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/SimpleVersionProvider.java new file mode 100644 index 000000000..3d77b1889 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/SimpleVersionProvider.java @@ -0,0 +1,47 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author huangyuhui + */ +public class SimpleVersionProvider implements VersionProvider { + + protected final Map versionMap = new HashMap<>(); + + @Override + public boolean hasVersion(String id) { + return versionMap.containsKey(id); + } + + @Override + public Version getVersion(String id) { + if (!hasVersion(id)) + throw new VersionNotFoundException("Version id " + id + " not found"); + else + return versionMap.get(id); + } + + public void addVersion(Version version) { + versionMap.put(version.getId(), version); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/StringArgument.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/StringArgument.java new file mode 100644 index 000000000..9c28522fc --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/StringArgument.java @@ -0,0 +1,56 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2013 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class StringArgument implements Argument { + + private final String argument; + + public StringArgument(String argument) { + this.argument = argument; + } + + public String getArgument() { + return argument; + } + + @Override + public List toString(Map keys, Map features) { + String res = argument; + Pattern pattern = Pattern.compile("\\$\\{(.*?)\\}"); + Matcher m = pattern.matcher(argument); + while (m.find()) { + String entry = m.group(); + res = res.replace(entry, keys.getOrDefault(entry, entry)); + } + return Collections.singletonList(res); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java new file mode 100644 index 000000000..94b894646 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -0,0 +1,265 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import com.google.gson.JsonParseException; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +@Immutable +public class Version implements Comparable, Validation { + + private final String id; + private final String minecraftArguments; + private final Arguments arguments; + private final String mainClass; + private final String inheritsFrom; + private final String jar; + private final AssetIndexInfo assetIndex; + private final String assets; + private final List libraries; + private final List compatibilityRules; + private final Map downloads; + private final Map logging; + private final ReleaseType type; + private final Date time; + private final Date releaseTime; + private final int minimumLauncherVersion; + + public Version(String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List libraries, List compatibilityRules, Map downloads, Map logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion) { + this.id = id; + this.minecraftArguments = minecraftArguments; + this.arguments = arguments; + this.mainClass = mainClass; + this.inheritsFrom = inheritsFrom; + this.jar = jar; + this.assetIndex = assetIndex; + this.assets = assets; + this.libraries = new LinkedList<>(libraries); + this.compatibilityRules = compatibilityRules == null ? null : new LinkedList<>(compatibilityRules); + this.downloads = downloads == null ? null : new HashMap<>(downloads); + this.logging = logging == null ? null : new HashMap<>(logging); + this.type = type; + this.time = time == null ? new Date() : (Date) time.clone(); + this.releaseTime = releaseTime == null ? new Date() : (Date) releaseTime.clone(); + this.minimumLauncherVersion = minimumLauncherVersion; + } + + public Optional getMinecraftArguments() { + return Optional.ofNullable(minecraftArguments); + } + + public Optional getArguments() { + return Optional.ofNullable(arguments); + } + + public String getMainClass() { + return mainClass; + } + + public Date getTime() { + return time; + } + + public String getId() { + return id; + } + + public ReleaseType getType() { + return type; + } + + public Date getReleaseTime() { + return releaseTime; + } + + public String getJar() { + return jar; + } + + public String getInheritsFrom() { + return inheritsFrom; + } + + public int getMinimumLauncherVersion() { + return minimumLauncherVersion; + } + + public Map getLogging() { + return logging == null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(logging); + } + + public List getLibraries() { + return libraries == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(libraries); + } + + public List getCompatibilityRules() { + return compatibilityRules == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(compatibilityRules); + } + + public DownloadInfo getDownloadInfo() { + DownloadInfo client = downloads == null ? null : downloads.get(DownloadType.CLIENT); + String jarName = jar == null ? id : jar; + if (client == null) + return new DownloadInfo(String.format("%s%s/%s.jar", Constants.DEFAULT_VERSION_DOWNLOAD_URL, jarName, jarName)); + else + return client; + } + + public AssetIndexInfo getAssetIndex() { + String assetsId = assets == null ? "legacy" : assets; + return assetIndex == null ? new AssetIndexInfo(assetsId, Constants.DEFAULT_INDEX_URL + assetsId + ".json") : assetIndex; + } + + public boolean appliesToCurrentEnvironment() { + return CompatibilityRule.appliesToCurrentEnvironment(compatibilityRules); + } + + public boolean install(String id) { + return false; + } + + /** + * Resolve given version + * + * @throws CircleDependencyException + */ + public Version resolve(VersionProvider provider) { + return resolve(provider, new HashSet<>()); + } + + protected Version resolve(VersionProvider provider, Set resolvedSoFar) { + if (inheritsFrom == null) + return this; + + // To maximize the compatibility. + if (!resolvedSoFar.add(id)) { + Logging.LOG.log(Level.WARNING, "Found circular dependency versions: {0}", resolvedSoFar); + return this; + } + + // It is supposed to auto install an version in getVersion. + Version parent = provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar); + return new Version(id, + minecraftArguments == null ? parent.minecraftArguments : minecraftArguments, + Arguments.merge(parent.arguments, arguments), + mainClass == null ? parent.mainClass : mainClass, + null, // inheritsFrom + jar == null ? parent.jar : jar, + assetIndex == null ? parent.assetIndex : assetIndex, + assets == null ? parent.assets : assets, + Lang.merge(parent.libraries, this.libraries), + Lang.merge(parent.compatibilityRules, this.compatibilityRules), + downloads == null ? parent.downloads : downloads, + logging == null ? parent.logging : logging, + type, + time, + releaseTime, + Math.max(minimumLauncherVersion, parent.minimumLauncherVersion)); + } + + public Version setId(String id) { + return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion); + } + + public Version setMinecraftArguments(String minecraftArguments) { + return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion); + } + + public Version setArguments(Arguments arguments) { + return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion); + } + + public Version setMainClass(String mainClass) { + return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion); + } + + public Version setInheritsFrom(String inheritsFrom) { + return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion); + } + + public Version setJar(String jar) { + return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion); + } + + public Version setLibraries(List libraries) { + return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion); + } + + public Version setLogging(Map logging) { + return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Version) + return Objects.equals(id, ((Version) obj).id); + else + return false; + } + + @Override + public int compareTo(Version o) { + return id.compareTo(o.id); + } + + @Override + public void validate() throws JsonParseException { + if (StringUtils.isBlank(id)) + throw new JsonParseException("Version ID cannot be blank"); + if (downloads != null) + for (Map.Entry entry : downloads.entrySet()) { + if (!(entry.getKey() instanceof DownloadType)) + throw new JsonParseException("Version downloads key must be DownloadType"); + if (!(entry.getValue() instanceof DownloadInfo)) + throw new JsonParseException("Version downloads value must be DownloadInfo"); + } + if (logging != null) + for (Map.Entry entry : logging.entrySet()) { + if (!(entry.getKey() instanceof DownloadType)) + throw new JsonParseException("Version logging key must be DownloadType"); + if (!(entry.getValue() instanceof LoggingInfo)) + throw new JsonParseException("Version logging value must be LoggingInfo"); + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionNotFoundException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionNotFoundException.java new file mode 100644 index 000000000..26220711a --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionNotFoundException.java @@ -0,0 +1,36 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +/** + * + * @author huangyuhui + */ +public final class VersionNotFoundException extends RuntimeException { + + public VersionNotFoundException() { + } + + public VersionNotFoundException(String message) { + super(message); + } + + public VersionNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/VersionProvider.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionProvider.java similarity index 83% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/VersionProvider.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionProvider.java index 21a89014b..fcfdd568a 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/VersionProvider.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionProvider.java @@ -15,27 +15,29 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.game +package org.jackhuang.hmcl.game; /** * Supports version accessing. * - * @see Version.resolve + * @see Version#resolve + * @author huangyuhui */ -interface VersionProvider { +public interface VersionProvider { /** * Does the version of id exist? + * * @param id the id of version * @return true if the version exists */ - fun hasVersion(id: String): Boolean + boolean hasVersion(String id); /** * Get the version + * * @param id the id of version * @return the version you want */ - fun getVersion(id: String): Version - -} \ No newline at end of file + Version getVersion(String id); +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java new file mode 100644 index 000000000..55cfe58be --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -0,0 +1,388 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.launch; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.jackhuang.hmcl.auth.AuthInfo; +import org.jackhuang.hmcl.game.Argument; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.game.DownloadType; +import org.jackhuang.hmcl.game.GameRepository; +import org.jackhuang.hmcl.game.LaunchOptions; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.LoggingInfo; +import org.jackhuang.hmcl.task.TaskResult; +import org.jackhuang.hmcl.util.CompressingUtils; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.JavaVersion; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Log4jLevel; +import org.jackhuang.hmcl.util.ManagedProcess; +import org.jackhuang.hmcl.util.OperatingSystem; +import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.StringUtils; + +/** + * + * @author huangyuhui + */ +public class DefaultLauncher extends Launcher { + + private List rawCommandLine; + protected final File nativeFolder; + + public DefaultLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) { + this(repository, versionId, authInfo, options, null); + } + + public DefaultLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { + this(repository, versionId, authInfo, options, listener, true); + } + + public DefaultLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { + super(repository, versionId, authInfo, options, listener, daemon); + + nativeFolder = repository.getNativeDirectory(versionId); + } + + @Override + public synchronized List getRawCommandLine() throws IOException { + if (rawCommandLine != null) + return Collections.unmodifiableList(rawCommandLine); + + List res = new LinkedList<>(); + + // Executable + if (StringUtils.isNotBlank(options.getWrapper())) + res.add(options.getWrapper()); + + res.add(options.getJava().getBinary().toString()); + + if (StringUtils.isNotBlank(options.getJavaArgs())) + res.addAll(StringUtils.tokenize(options.getJavaArgs())); + + // JVM Args + if (!options.isNoGeneratedJVMArgs()) { + appendJvmArgs(res); + + res.add("-Dminecraft.client.jar=" + repository.getVersionJar(version)); + + if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { + res.add("-Xdock:name=Minecraft " + version.getId()); + res.add("-Xdock:icon=" + repository.getAssetObject(version.getId(), version.getAssetIndex().getId(), "icons/minecraft.icns").getAbsolutePath()); + } + + Map logging = version.getLogging(); + if (logging != null) { + LoggingInfo loggingInfo = logging.get(DownloadType.CLIENT); + if (loggingInfo != null) { + File loggingFile = repository.getLoggingObject(version.getId(), version.getAssetIndex().getId(), loggingInfo); + if (loggingFile.exists()) + res.add(loggingInfo.getArgument().replace("${path}", loggingFile.getAbsolutePath())); + } + } + + if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) + res.add("-Duser.home=" + options.getGameDir().getParent()); + + if (options.getJava().getParsedVersion() >= JavaVersion.JAVA_7) + res.add("-XX:+UseG1GC"); + + if (options.getMetaspace() != null && options.getMetaspace() > 0) + if (options.getJava().getParsedVersion() < JavaVersion.JAVA_8) + res.add("-XX:PermSize= " + options.getMetaspace() + "m"); + else + res.add("-XX:MetaspaceSize=" + options.getMetaspace() + "m"); + + res.add("-XX:-UseAdaptiveSizePolicy"); + res.add("-XX:-OmitStackTraceInFastThrow"); + res.add("-Xmn128m"); + + if (options.getMaxMemory() != null && options.getMaxMemory() > 0) + res.add("-Xmx" + options.getMaxMemory() + "m"); + + if (options.getMinMemory() != null && options.getMinMemory() > 0) + res.add("-Xms" + options.getMinMemory() + "m"); + + res.add("-Dfml.ignoreInvalidMinecraftCertificates=true"); + res.add("-Dfml.ignorePatchDiscrepancies=true"); + } + + LinkedList lateload = new LinkedList<>(); + StringBuilder classpath = new StringBuilder(); + for (Library library : version.getLibraries()) + if (library.appliesToCurrentEnvironment() && !library.isNative()) { + File f = repository.getLibraryFile(version, library); + if (f.exists() && f.isFile()) + if (library.isLateload()) + lateload.add(f); + else + classpath.append(f.getAbsolutePath()).append(OperatingSystem.PATH_SEPARATOR); + } + for (File library : lateload) + classpath.append(library.getAbsolutePath()).append(OperatingSystem.PATH_SEPARATOR); + + File jar = repository.getVersionJar(version); + if (!jar.exists() || !jar.isFile()) + throw new IOException("Minecraft jar does not exist"); + classpath.append(jar.getAbsolutePath()); + + // Provided Minecraft arguments + File gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId()); + Map configuration = getConfigurations(); + configuration.put("${classpath}", classpath.toString()); + configuration.put("${natives_directory}", nativeFolder.getAbsolutePath()); + configuration.put("${game_assets}", gameAssets.getAbsolutePath()); + configuration.put("${assets_root}", gameAssets.getAbsolutePath()); + + res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration)); + res.add(version.getMainClass()); + + Map features = getFeatures(); + res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getGame).orElseGet(this::getDefaultGameArguments), configuration, features)); + res.addAll(Arguments.parseStringArguments(version.getMinecraftArguments().map(StringUtils::tokenize).orElseGet(LinkedList::new), configuration)); + + // Optional Minecraft arguments + if (options.getHeight() != null && options.getHeight() != 0 && options.getWidth() != null && options.getWidth() != 0) { + res.add("--height"); + res.add(options.getHeight().toString()); + res.add("--width"); + res.add(options.getWidth().toString()); + } + + if (StringUtils.isNotBlank(options.getServerIp())) { + String[] args = options.getServerIp().split(":"); + res.add("--server"); + res.add(args[0]); + res.add("--port"); + res.add(args.length > 1 ? args[1] : "25565"); + } + + if (options.isFullscreen()) + res.add("--fullscreen"); + + if (StringUtils.isNotBlank(options.getProxyHost()) && StringUtils.isNotBlank(options.getProxyPort())) { + res.add("--proxyHost"); + res.add(options.getProxyHost()); + res.add("--proxyPort"); + res.add(options.getProxyPort()); + if (StringUtils.isNotBlank(options.getProxyUser()) && StringUtils.isNotBlank(options.getProxyPass())) { + res.add("--proxyUser"); + res.add(options.getProxyUser()); + res.add("--proxyPass"); + res.add(options.getProxyPass()); + } + } + + if (StringUtils.isNotBlank(options.getMinecraftArgs())) + res.addAll(StringUtils.tokenize(options.getMinecraftArgs())); + + return res.stream() + .filter(it -> !getForbiddens().containsKey(it) || !getForbiddens().get(it).get()) + .collect(Collectors.toList()); + } +//http://jenkins.liteloader.com/job/LiteLoader%201.12.2/lastSuccessfulBuild/artifact/build/libs/liteloader-1.12.2-SNAPSHOT-release.jar + public Map getFeatures() { + return Collections.singletonMap( + "has_custom_resolution", + options.getHeight() != null && options.getHeight() != 0 && options.getWidth() != null && options.getWidth() != 0 + ); + } + + private final Map> forbiddens = Lang.mapOf( + new Pair<>("-Xincgc", () -> options.getJava().getParsedVersion() >= JavaVersion.JAVA_9) + ); + + protected Map> getForbiddens() { + return forbiddens; + } + + protected List getDefaultJVMArguments() { + return Arguments.DEFAULT_JVM_ARGUMENTS; + } + + protected List getDefaultGameArguments() { + return Arguments.DEFAULT_GAME_ARGUMENTS; + } + + /** + * Do something here. + * i.e. + * -Dminecraft.launcher.version=<Your launcher name> + * -Dminecraft.launcher.brand=<Your launcher version> + * -Dlog4j.configurationFile=<Your custom log4j configuration> + */ + protected void appendJvmArgs(List result) { + } + + public void decompressNatives() throws IOException { + for (Library library : version.getLibraries()) + if (library.isNative()) + CompressingUtils.unzip(repository.getLibraryFile(version, library), + nativeFolder, + "", + library.getExtract()::shouldExtract, + false); + } + + public Map getConfigurations() { + return Lang.mapOf( + new Pair<>("${auth_player_name}", authInfo.getUsername()), + new Pair<>("${auth_session}", authInfo.getAuthToken()), + new Pair<>("${auth_access_token}", authInfo.getAuthToken()), + new Pair<>("${auth_uuid}", authInfo.getUserId()), + new Pair<>("${version_name}", Lang.nonNull(options.getVersionName(), version.getId())), + new Pair<>("${profile_name}", Lang.nonNull(options.getProfileName(), "Minecraft")), + new Pair<>("${version_type}", version.getType().getId()), + new Pair<>("${game_directory}", repository.getRunDirectory(version.getId()).getAbsolutePath()), + new Pair<>("${user_type}", authInfo.getUserType().toString().toLowerCase()), + new Pair<>("${assets_index_name}", version.getAssetIndex().getId()), + new Pair<>("${user_properties}", authInfo.getUserProperties()) + ); + } + + @Override + public ManagedProcess launch() throws IOException, InterruptedException { + + // To guarantee that when failed to generate code, we will not call precalled command + ProcessBuilder builder = new ProcessBuilder(getRawCommandLine()); + + decompressNatives(); + + if (StringUtils.isNotBlank(options.getPrecalledCommand())) { + Process process = Runtime.getRuntime().exec(options.getPrecalledCommand()); + if (process.isAlive()) + process.waitFor(); + } + + builder.directory(repository.getRunDirectory(version.getId())) + .environment().put("APPDATA", options.getGameDir().getAbsoluteFile().getParent()); + ManagedProcess p = new ManagedProcess(builder.start(), getRawCommandLine()); + if (listener == null) + startMonitors(p); + else + startMonitors(p, listener, daemon); + return p; + } + + public final TaskResult launchAsync() { + return new TaskResult() { + @Override + public String getId() { + return LAUNCH_ASYNC_ID; + } + + @Override + public void execute() throws Exception { + setResult(launch()); + } + }; + } + + @Override + public File makeLaunchScript(String file) throws IOException { + boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS; + File scriptFile = new File(file + (isWindows ? ".bat" : ".sh")); + if (!FileUtils.makeFile(scriptFile)) + throw new IOException("Script file: " + scriptFile + " cannot be created."); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(scriptFile)))) { + if (isWindows) { + writer.write("@echo off"); + writer.newLine(); + writer.write("set APPDATA=" + options.getGameDir().getParent()); + writer.newLine(); + writer.write("cd /D %APPDATA%"); + writer.newLine(); + } + if (StringUtils.isNotBlank(options.getPrecalledCommand())) { + writer.write(options.getPrecalledCommand()); + writer.newLine(); + } + writer.write(StringUtils.makeCommand(getRawCommandLine())); + } + if (!scriptFile.setExecutable(true)) + throw new IOException("Cannot make script file '" + scriptFile + "' executable."); + return scriptFile; + } + + private void startMonitors(ManagedProcess managedProcess) { + managedProcess.addRelatedThread(Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream()), "stdout-pump", true)); + managedProcess.addRelatedThread(Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream()), "stderr-pump", true)); + } + + private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener) { + startMonitors(managedProcess, processListener, true); + } + + private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) { + boolean enablesLoggingInfo = version.getLogging() != null && version.getLogging().containsKey(DownloadType.CLIENT); + if (enablesLoggingInfo) + startMonitorsWithLoggingInfo(managedProcess, processListener, isDaemon); + else + startMonitorsWithoutLoggingInfo(managedProcess, processListener, isDaemon); + } + + private void startMonitorsWithLoggingInfo(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) { + processListener.setProcess(managedProcess); + Log4jHandler logHandler = new Log4jHandler((line, level) -> { + processListener.onLog(line, level); + managedProcess.addLine(line); + }); + logHandler.start(); + managedProcess.addRelatedThread(logHandler); + Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), logHandler::newLine), "stdout-pump", isDaemon); + managedProcess.addRelatedThread(stdout); + Thread stderr = Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream(), it -> { + processListener.onLog(it + OperatingSystem.LINE_SEPARATOR, Log4jLevel.ERROR); + managedProcess.addLine(it); + }), "stderr-pump", isDaemon); + managedProcess.addRelatedThread(stderr); + managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), (exitCode, exitType) -> { + logHandler.onStopped(); + processListener.onExit(exitCode, exitType); + }), "exit-waiter", isDaemon)); + } + + private void startMonitorsWithoutLoggingInfo(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) { + processListener.setProcess(managedProcess); + Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> { + processListener.onLog(it + OperatingSystem.LINE_SEPARATOR, Lang.nonNull(Log4jLevel.guessLevel(it), Log4jLevel.INFO)); + managedProcess.addLine(it); + }), "stdout-pump", isDaemon); + managedProcess.addRelatedThread(stdout); + Thread stderr = Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream(), it -> { + processListener.onLog(it + OperatingSystem.LINE_SEPARATOR, Log4jLevel.ERROR); + managedProcess.addLine(it); + }), "stderr-pump", isDaemon); + managedProcess.addRelatedThread(stderr); + managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), (exitCode, exitType) -> processListener.onExit(exitCode, exitType)), "exit-waiter", isDaemon)); + } + + public static final String LAUNCH_ASYNC_ID = "process"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java new file mode 100644 index 000000000..8e9ac791d --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java @@ -0,0 +1,88 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.launch; + +import java.util.Collection; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import org.jackhuang.hmcl.event.EventBus; +import org.jackhuang.hmcl.event.JVMLaunchFailedEvent; +import org.jackhuang.hmcl.event.ProcessExitedAbnormallyEvent; +import org.jackhuang.hmcl.event.ProcessStoppedEvent; +import org.jackhuang.hmcl.util.Log4jLevel; +import org.jackhuang.hmcl.util.ManagedProcess; +import org.jackhuang.hmcl.util.StringUtils; + +/** + * + * @author huangyuhui + */ +final class ExitWaiter implements Runnable { + + private final ManagedProcess process; + private final Collection joins; + private final BiConsumer watcher; + + /** + * Constructor. + * + * @param process the process to wait for + * @param watcher the callback that will be called after process stops. + */ + public ExitWaiter(ManagedProcess process, Collection joins, BiConsumer watcher) { + this.process = process; + this.joins = joins; + this.watcher = watcher; + } + + @Override + public void run() { + try { + int exitCode = process.getProcess().waitFor(); + + for (Thread thread : joins) + thread.join(); + + List errorLines = process.getLines().stream() + .filter(Log4jLevel::guessLogLineError).collect(Collectors.toList()); + ProcessListener.ExitType exitType; + + // LaunchWrapper will catch the exception logged and will exit normally. + if (exitCode != 0 || StringUtils.containsOne(errorLines, "Unable to launch")) { + EventBus.EVENT_BUS.fireEvent(new ProcessExitedAbnormallyEvent(this, process)); + exitType = ProcessListener.ExitType.APPLICATION_ERROR; + } else if (exitCode != 0 && StringUtils.containsOne(errorLines, + "Could not create the Java Virtual Machine.", + "Error occurred during initialization of VM", + "A fatal exception has occurred. Program will exit.", + "Unable to launch")) { + EventBus.EVENT_BUS.fireEvent(new JVMLaunchFailedEvent(this, process)); + exitType = ProcessListener.ExitType.JVM_ERROR; + } else + exitType = ProcessListener.ExitType.NORMAL; + + EventBus.EVENT_BUS.fireEvent(new ProcessStoppedEvent(this, process)); + + watcher.accept(exitCode, exitType); + } catch (InterruptedException e) { + watcher.accept(1, ProcessListener.ExitType.NORMAL); + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.java new file mode 100644 index 000000000..ce7870e7d --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.java @@ -0,0 +1,73 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.launch; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import org.jackhuang.hmcl.auth.AuthInfo; +import org.jackhuang.hmcl.game.GameException; +import org.jackhuang.hmcl.game.GameRepository; +import org.jackhuang.hmcl.game.LaunchOptions; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.util.ManagedProcess; + +/** + * + * @author huangyuhui + */ +public abstract class Launcher { + + protected final GameRepository repository; + protected final String versionId; + protected final Version version; + protected final AuthInfo authInfo; + protected final LaunchOptions options; + protected final ProcessListener listener; + protected final boolean daemon; + + public Launcher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) { + this(repository, versionId, authInfo, options, null); + } + + public Launcher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) { + this(repository, versionId, authInfo, options, listener, true); + } + + public Launcher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) { + this.repository = repository; + this.versionId = versionId; + this.authInfo = authInfo; + this.options = options; + this.listener = listener; + this.daemon = daemon; + + version = repository.getVersion(versionId).resolve(repository); + } + + /** + * @param file The file path without extension + * @return the actual file with extension sh or bat. + */ + public abstract File makeLaunchScript(String file) throws IOException; + + public abstract ManagedProcess launch() throws IOException, InterruptedException; + + public abstract List getRawCommandLine() throws IOException; + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Log4jHandler.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Log4jHandler.java new file mode 100644 index 000000000..719ee01bd --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Log4jHandler.java @@ -0,0 +1,161 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.launch; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.Date; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Log4jLevel; +import org.jackhuang.hmcl.util.OperatingSystem; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * This class is to parse log4j classic XML layout logging, + * since only vanilla Minecraft will enable this layout. + * + * Also supports plain logs. + * + * @author huangyuhui + */ +final class Log4jHandler extends Thread { + + private final XMLReader reader; + private final BiConsumer callback; + private final PipedOutputStream outputStream = new PipedOutputStream(); + private final PipedInputStream inputStream = Lang.invoke(() -> new PipedInputStream(outputStream)); + private final AtomicBoolean interrupted = new AtomicBoolean(false); + + public Log4jHandler(BiConsumer callback) { + this.callback = callback; + + reader = Lang.invoke(() -> XMLReaderFactory.createXMLReader()); + reader.setContentHandler(new Log4jHandlerImpl()); + } + + @Override + public void run() { + setName("log4j-handler"); + newLine(""); + + try { + reader.parse(new InputSource(inputStream)); + } catch (InterruptedIOException e) { + // Game has been interrupted. + interrupted.set(true); + } catch (SAXException | IOException e) { + Lang.throwable(e); + } + } + + public void onStopped() { + if (interrupted.get()) + return; + + Lang.invoke(() -> Schedulers.newThread().schedule(() -> { + if (!interrupted.get()) { + Lang.invoke(() -> newLine("").get()); + outputStream.close(); + join(); + } + }).get()); + } + + public Future newLine(String content) { + return Schedulers.computation().schedule(() -> { + String log = content; + if (!log.trim().startsWith("<")) + log = "", "") + "]]>"; + outputStream.write((log + OperatingSystem.LINE_SEPARATOR) + .replace("log4j:Event", "log4j_Event") + .replace("log4j:Message", "log4j_Message") + .replace("log4j:Throwable", "log4j_Throwable") + .getBytes() + ); + outputStream.flush(); + }); + } + + private class Log4jHandlerImpl extends DefaultHandler { + + private String date = "", thread = "", logger = ""; + private StringBuilder message; + private Log4jLevel level; + private boolean readingMessage = false; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + switch (localName) { + case "log4j_Event": + message = new StringBuilder(); + Date d = new Date(Long.valueOf(attributes.getValue("timestamp"))); + date = Constants.DEFAULT_DATE_FORMAT.format(d); + try { + level = Log4jLevel.valueOf(attributes.getValue("level")); + } catch (IllegalArgumentException e) { + level = Log4jLevel.INFO; + } + thread = attributes.getValue("thread"); + logger = attributes.getValue("logger"); + if ("STDERR".equals(logger)) + level = Log4jLevel.ERROR; + break; + case "log4j_Message": + readingMessage = true; + break; + case "log4j_Throwable": + break; + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + switch (localName) { + case "log4j_Event": + callback.accept("[" + date + "] [" + thread + "/" + level.name() + "] [" + logger + "] " + message.toString(), level); + break; + case "log4j_Message": + readingMessage = false; + break; + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + String line = new String(ch, start, length); + if (line.trim().isEmpty()) + return; + if (readingMessage) + message.append(line).append(OperatingSystem.LINE_SEPARATOR); + else + callback.accept(line, Lang.nonNull(Log4jLevel.guessLevel(line), Log4jLevel.INFO)); + } + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/ProcessListener.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ProcessListener.java similarity index 77% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/ProcessListener.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ProcessListener.java index b31dfd204..e30de6e17 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/ProcessListener.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ProcessListener.java @@ -15,17 +15,23 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.launch +package org.jackhuang.hmcl.launch; -import org.jackhuang.hmcl.util.Log4jLevel -import org.jackhuang.hmcl.util.ManagedProcess +import org.jackhuang.hmcl.util.Log4jLevel; +import org.jackhuang.hmcl.util.ManagedProcess; + +/** + * + * @author huangyuhui + */ +public interface ProcessListener { -interface ProcessListener { /** * When a game launched, this method will be called to get the new process. * You should not override this method when your ProcessListener is shared with all processes. */ - fun setProcess(process: ManagedProcess) {} + default void setProcess(ManagedProcess process) { + } /** * Called when receiving a log from stdout/stderr. @@ -34,18 +40,18 @@ interface ProcessListener { * * @param log the log */ - fun onLog(log: String, level: Log4jLevel) + void onLog(String log, Log4jLevel level); /** * Called when the game process stops. * * @param exitCode the exit code */ - fun onExit(exitCode: Int, exitType: ExitType) + void onExit(int exitCode, ExitType exitType); - enum class ExitType { + enum ExitType { JVM_ERROR, APPLICATION_ERROR, NORMAL } -} \ No newline at end of file +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/StreamPump.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/StreamPump.java new file mode 100644 index 000000000..2bc61b891 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/StreamPump.java @@ -0,0 +1,68 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.launch; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.function.Consumer; +import java.util.logging.Level; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.Logging; + +/** + * Pump the given input stream. + * + * @author huangyuhui + */ +final class StreamPump implements Runnable { + + private final InputStream inputStream; + private final Consumer callback; + + public StreamPump(InputStream inputStream) { + this(inputStream, s -> { + }); + } + + public StreamPump(InputStream inputStream, Consumer callback) { + this.inputStream = inputStream; + this.callback = callback; + } + + @Override + public void run() { + try { + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, Constants.SYSTEM_CHARSET))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + if (Thread.currentThread().isInterrupted()) { + Thread.currentThread().interrupt(); + break; + } + + callback.accept(line); + } + } + } catch (IOException e) { + Logging.LOG.log(Level.SEVERE, "An error occurred when reading stream", e); + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java new file mode 100644 index 000000000..6e4f65ea4 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseCompletionTask.java @@ -0,0 +1,121 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import java.io.File; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.game.GameRepository; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.jackhuang.hmcl.util.StringUtils; + +/** + * Complete the CurseForge version. + * + * @author huangyuhui + */ +public final class CurseCompletionTask extends Task { + + private final DefaultDependencyManager dependencyManager; + private final GameRepository repository; + private final String version; + private CurseManifest manifest = null; + private final List dependents = new LinkedList<>(); + private final List dependencies = new LinkedList<>(); + + /** + * Constructor. + * + * @param dependencyManager the dependency manager. + * @param version the existent and physical version. + */ + public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version) { + this(dependencyManager, version, null); + } + + /** + * Constructor. + * + * @param dependencyManager the dependency manager. + * @param version the existent and physical version. + * @param manifest the CurseForgeModpack manifest. + */ + public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version, CurseManifest manifest) { + this.dependencyManager = dependencyManager; + this.repository = dependencyManager.getGameRepository(); + this.version = version; + this.manifest = manifest; + + if (manifest == null) + try { + File manifestFile = new File(repository.getVersionRoot(version), "manifest.json"); + if (manifestFile.exists()) + this.manifest = Constants.GSON.fromJson(FileUtils.readText(manifestFile), CurseManifest.class); + } catch (Exception e) { + Logging.LOG.log(Level.WARNING, "Unable to read CurseForge modpack manifest.json", e); + } + } + + @Override + public Collection getDependencies() { + return dependencies; + } + + @Override + public Collection getDependents() { + return dependents; + } + + @Override + public void execute() throws Exception { + if (manifest == null) + return; + + File root = repository.getVersionRoot(version); + File run = repository.getRunDirectory(version); + + AtomicInteger finished = new AtomicInteger(0); + + // Because in China, Curse is too difficult to visit, + // if failed, ignore it and retry next time. + CurseManifest newManifest = manifest.setFiles( + manifest.getFiles().parallelStream() + .map(file -> { + updateProgress(finished.incrementAndGet(), manifest.getFiles().size()); + return Lang.ignoringException(() -> file.setFileName(NetworkUtils.detectFileName(file.getUrl(), dependencyManager.getProxy())), file); + }) + .collect(Collectors.toList())); + FileUtils.writeText(new File(root, "manifest.json"), Constants.GSON.toJson(newManifest)); + + for (CurseManifestFile file : newManifest.getFiles()) + if (StringUtils.isNotBlank(file.getFileName())) + dependencies.add(new FileDownloadTask(file.getUrl(), new File(run, "mods/" + file.getFileName()), dependencyManager.getProxy())); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseInstallTask.java new file mode 100644 index 000000000..fa22adbf1 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseInstallTask.java @@ -0,0 +1,90 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import java.io.File; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.GameBuilder; +import org.jackhuang.hmcl.game.DefaultGameRepository; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.CompressingUtils; + +/** + * Install a downloaded CurseForge modpack. + * + * @author huangyuhui + */ +public final class CurseInstallTask extends Task { + + private final DefaultDependencyManager dependencyManager; + private final DefaultGameRepository repository; + private final File zipFile; + private final CurseManifest manifest; + private final String name; + private final List dependents = new LinkedList<>(); + private final List dependencies = new LinkedList<>(); + + /** + * Constructor. + * + * @param dependencyManager the dependency manager. + * @param zipFile the CurseForge modpack file. + * @param manifest The manifest content of given CurseForge modpack. + * @param name the new version name + * @see readCurseForgeModpackManifest + */ + public CurseInstallTask(DefaultDependencyManager dependencyManager, File zipFile, CurseManifest manifest, String name) { + this.dependencyManager = dependencyManager; + this.zipFile = zipFile; + this.manifest = manifest; + this.name = name; + + repository = dependencyManager.getGameRepository(); + + if (repository.hasVersion(name)) + throw new IllegalArgumentException("Version " + name + " already exists."); + + GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getMinecraft().getGameVersion()); + for (CurseManifestModLoader modLoader : manifest.getMinecraft().getModLoaders()) + if (modLoader.getId().startsWith("forge-")) + builder.version("forge", modLoader.getId().substring("forge-".length())); + dependents.add(builder.buildAsync()); + } + + @Override + public Collection getDependents() { + return dependents; + } + + @Override + public Collection getDependencies() { + return dependencies; + } + + @Override + public void execute() throws Exception { + File run = repository.getRunDirectory(name); + CompressingUtils.unzip(zipFile, run, manifest.getOverrides()); + + dependencies.add(new CurseCompletionTask(dependencyManager, name)); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifest.java new file mode 100644 index 000000000..b504938fb --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifest.java @@ -0,0 +1,129 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import org.jackhuang.hmcl.util.CompressingUtils; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class CurseManifest { + + @SerializedName("manifestType") + private final String manifestType; + + @SerializedName("manifestVersion") + private final int manifestVersion; + + @SerializedName("name") + private final String name; + + @SerializedName("version") + private final String version; + + @SerializedName("author") + private final String author; + + @SerializedName("overrides") + private final String overrides; + + @SerializedName("minecraft") + private final CurseManifestMinecraft minecraft; + + @SerializedName("files") + private final List files; + + public CurseManifest() { + this(MINECRAFT_MODPACK, 1, "", "1.0", "", "overrides", new CurseManifestMinecraft(), Collections.EMPTY_LIST); + } + + public CurseManifest(String manifestType, int manifestVersion, String name, String version, String author, String overrides, CurseManifestMinecraft minecraft, List files) { + this.manifestType = manifestType; + this.manifestVersion = manifestVersion; + this.name = name; + this.version = version; + this.author = author; + this.overrides = overrides; + this.minecraft = minecraft; + this.files = files; + } + + public String getManifestType() { + return manifestType; + } + + public int getManifestVersion() { + return manifestVersion; + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } + + public String getAuthor() { + return author; + } + + public String getOverrides() { + return overrides; + } + + public CurseManifestMinecraft getMinecraft() { + return minecraft; + } + + public List getFiles() { + return files; + } + + public CurseManifest setFiles(List files) { + return new CurseManifest(manifestType, manifestVersion, name, version, author, overrides, minecraft, files); + } + + /** + * @param f the CurseForge modpack file. + * @throws IOException if the file is not a valid zip file. + * @throws JsonParseException if the manifest.json is missing or malformed. + * @return the manifest. + */ + public static Modpack readCurseForgeModpackManifest(File f) throws IOException, JsonParseException { + String json = CompressingUtils.readTextZipEntry(f, "manifest.json"); + CurseManifest manifest = Constants.GSON.fromJson(json, CurseManifest.class); + if (manifest == null) + throw new JsonParseException("`manifest.json` not found. Not a valid Curse modpack."); + return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), manifest.getMinecraft().getGameVersion(), + Lang.nonNull(CompressingUtils.readTextZipEntryQuietly(f, "modlist.html"), "No description"), manifest); + } + + public static final String MINECRAFT_MODPACK = "minecraftModpack"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestFile.java new file mode 100644 index 000000000..818c9eabe --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestFile.java @@ -0,0 +1,86 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import java.net.URL; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class CurseManifestFile implements Validation { + + @SerializedName("projectID") + private final int projectID; + + @SerializedName("fileID") + private final int fileID; + + @SerializedName("fileName") + private final String fileName; + + @SerializedName("required") + private final boolean required; + + public CurseManifestFile() { + this(0, 0, "", true); + } + + public CurseManifestFile(int projectID, int fileID, String fileName, boolean required) { + this.projectID = projectID; + this.fileID = fileID; + this.fileName = fileName; + this.required = required; + } + + public int getProjectID() { + return projectID; + } + + public int getFileID() { + return fileID; + } + + public String getFileName() { + return fileName; + } + + public boolean isRequired() { + return required; + } + + @Override + public void validate() throws JsonParseException { + if (projectID == 0 || fileID == 0) + throw new JsonParseException("Missing Project ID or File ID."); + } + + public URL getUrl() { + return NetworkUtils.toURL("https://minecraft.curseforge.com/projects/" + projectID + "/files/" + fileID + "/download"); + } + + public CurseManifestFile setFileName(String fileName) { + return new CurseManifestFile(projectID, fileID, fileName, required); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestMinecraft.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestMinecraft.java new file mode 100644 index 000000000..f3243e42a --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestMinecraft.java @@ -0,0 +1,66 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class CurseManifestMinecraft implements Validation { + + @SerializedName("version") + private final String gameVersion; + + @SerializedName("modLoaders") + private final List modLoaders; + + public CurseManifestMinecraft() { + this.gameVersion = ""; + this.modLoaders = Collections.EMPTY_LIST; + } + + public CurseManifestMinecraft(String gameVersion, List modLoaders) { + this.gameVersion = gameVersion; + this.modLoaders = new LinkedList<>(modLoaders); + } + + public String getGameVersion() { + return gameVersion; + } + + public List getModLoaders() { + return Collections.unmodifiableList(modLoaders); + } + + @Override + public void validate() throws JsonParseException { + if (StringUtils.isBlank(gameVersion)) + throw new JsonParseException("CurseForge Manifest.gameVersion cannot be blank."); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestModLoader.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestModLoader.java new file mode 100644 index 000000000..5749d8bed --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifestModLoader.java @@ -0,0 +1,62 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.Validation; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class CurseManifestModLoader implements Validation { + + @SerializedName("id") + private final String id; + + @SerializedName("primary") + private final boolean primary; + + public CurseManifestModLoader() { + this("", false); + } + + public CurseManifestModLoader(String id, boolean primary) { + this.id = id; + this.primary = primary; + } + + public String getId() { + return id; + } + + public boolean isPrimary() { + return primary; + } + + @Override + public void validate() throws JsonParseException { + if (StringUtils.isBlank(id)) + throw new JsonParseException("Curse Forge modpack manifest Mod loader id cannot be blank."); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeModMetadata.java new file mode 100644 index 000000000..ff3bfc5c7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeModMetadata.java @@ -0,0 +1,139 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.IOUtils; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.StringUtils; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class ForgeModMetadata { + + @SerializedName("modid") + private final String modId; + private final String name; + private final String description; + private final String author; + private final String version; + private final String mcversion; + private final String url; + private final String updateUrl; + private final String credits; + private final String[] authorList; + private final String[] authors; + + public ForgeModMetadata() { + this("", "", "", "", "", "", "", "", "", new String[0], new String[0]); + } + + public ForgeModMetadata(String modId, String name, String description, String author, String version, String mcversion, String url, String updateUrl, String credits, String[] authorList, String[] authors) { + this.modId = modId; + this.name = name; + this.description = description; + this.author = author; + this.version = version; + this.mcversion = mcversion; + this.url = url; + this.updateUrl = updateUrl; + this.credits = credits; + this.authorList = authorList; + this.authors = authors; + } + + public String getModId() { + return modId; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getAuthor() { + return author; + } + + public String getVersion() { + return version; + } + + public String getGameVersion() { + return mcversion; + } + + public String getUrl() { + return url; + } + + public String getUpdateUrl() { + return updateUrl; + } + + public String getCredits() { + return credits; + } + + public String[] getAuthorList() { + return authorList; + } + + public String[] getAuthors() { + return authors; + } + + public static ModInfo fromFile(File modFile) throws IOException, JsonParseException { + try (ZipFile zipFile = new ZipFile(modFile)) { + ZipArchiveEntry entry = zipFile.getEntry("mcmod.info"); + if (entry == null) + throw new IOException("File " + modFile + " is not a Forge mod."); + List modList = Constants.GSON.fromJson(IOUtils.readFullyAsString(zipFile.getInputStream(entry)), + new TypeToken>() { + }.getType()); + if (modList == null || modList.isEmpty()) + throw new IOException("Mod " + modFile + " `mcmod.info` is malformed.."); + ForgeModMetadata metadata = modList.get(0); + String authors = metadata.getAuthor(); + if (StringUtils.isBlank(authors) && metadata.getAuthors().length > 0) + authors = String.join(", ", metadata.getAuthors()); + if (StringUtils.isBlank(authors) && metadata.getAuthorList().length > 0) + authors = String.join(", ", metadata.getAuthorList()); + if (StringUtils.isBlank(authors)) + authors = metadata.getCredits(); + return new ModInfo(modFile, metadata.getName(), metadata.getDescription(), + authors, metadata.getVersion(), metadata.getGameVersion(), + StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java new file mode 100644 index 000000000..dd16fde6e --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java @@ -0,0 +1,121 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import com.google.gson.JsonParseException; +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.IOUtils; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class LiteModMetadata { + private final String name; + private final String version; + private final String mcversion; + private final String revision; + private final String author; + private final String classTransformerClasses; + private final String description; + private final String modpackName; + private final String modpackVersion; + private final String checkUpdateUrl; + private final String updateURI; + + public LiteModMetadata() { + this("", "", "", "", "", "", "", "", "", "", ""); + } + + public LiteModMetadata(String name, String version, String mcversion, String revision, String author, String classTransformerClasses, String description, String modpackName, String modpackVersion, String checkUpdateUrl, String updateURI) { + this.name = name; + this.version = version; + this.mcversion = mcversion; + this.revision = revision; + this.author = author; + this.classTransformerClasses = classTransformerClasses; + this.description = description; + this.modpackName = modpackName; + this.modpackVersion = modpackVersion; + this.checkUpdateUrl = checkUpdateUrl; + this.updateURI = updateURI; + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } + + public String getGameVersion() { + return mcversion; + } + + public String getRevision() { + return revision; + } + + public String getAuthor() { + return author; + } + + public String getClassTransformerClasses() { + return classTransformerClasses; + } + + public String getDescription() { + return description; + } + + public String getModpackName() { + return modpackName; + } + + public String getModpackVersion() { + return modpackVersion; + } + + public String getCheckUpdateUrl() { + return checkUpdateUrl; + } + + public String getUpdateURI() { + return updateURI; + } + + public static ModInfo fromFile(File modFile) throws IOException, JsonParseException { + try (ZipFile zipFile = new ZipFile(modFile)) { + ZipEntry entry = zipFile.getEntry("litemod.json"); + if (entry == null) + throw new IOException("File " + modFile + "is not a LiteLoader mod."); + LiteModMetadata metadata = Constants.GSON.fromJson(IOUtils.readFullyAsString(zipFile.getInputStream(entry)), LiteModMetadata.class); + if (metadata == null) + throw new IOException("Mod " + modFile + " `litemod.json` is malformed."); + return new ModInfo(modFile, metadata.getName(), metadata.getDescription(), metadata.getAuthor(), metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI()); + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java new file mode 100644 index 000000000..5ab3223e7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java @@ -0,0 +1,158 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import java.io.File; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.ImmediateBooleanProperty; +import org.jackhuang.hmcl.util.StringUtils; + +/** + * + * @author huangyuhui + */ +public final class ModInfo implements Comparable { + + private File file; + private final String name; + private final String description; + private final String authors; + private final String version; + private final String gameVersion; + private final String url; + private final String fileName; + private final ImmediateBooleanProperty activeProperty; + + public ModInfo(File file, String name) { + this(file, name, ""); + } + + public ModInfo(File file, String name, String description) { + this(file, name, description, "unknown", "unknown", "unknown", ""); + } + + public ModInfo(File file, String name, String description, String authors, String version, String gameVersion, String url) { + this.file = file; + this.name = name; + this.description = description; + this.authors = authors; + this.version = version; + this.gameVersion = gameVersion; + this.url = url; + + activeProperty = new ImmediateBooleanProperty(this, "active", !DISABLED_EXTENSION.equals(FileUtils.getExtension(file))) { + @Override + protected void invalidated() { + File f = file.getAbsoluteFile(), newF; + if (DISABLED_EXTENSION.equals(FileUtils.getExtension(f))) + newF = new File(f.getParentFile(), FileUtils.getNameWithoutExtension(f)); + else + newF = new File(f.getParentFile(), f.getName() + ".disabled"); + } + }; + + fileName = StringUtils.substringBeforeLast(isActive() ? file.getName() : FileUtils.getNameWithoutExtension(file), '.'); + } + + public File getFile() { + return file; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getAuthors() { + return authors; + } + + public String getVersion() { + return version; + } + + public String getGameVersion() { + return gameVersion; + } + + public String getUrl() { + return url; + } + + public ImmediateBooleanProperty activeProperty() { + return activeProperty; + } + + public boolean isActive() { + return activeProperty.get(); + } + + public void setActive(boolean active) { + activeProperty.set(active); + } + + public String getFileName() { + return fileName; + } + + @Override + public int compareTo(ModInfo o) { + return getFileName().compareTo(o.getFileName()); + } + + public static boolean isFileMod(File file) { + String name = file.getName(); + if (isDisabled(file)) + name = FileUtils.getNameWithoutExtension(file); + return name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".litemod"); + } + + public static ModInfo fromFile(File modFile) { + File file = isDisabled(modFile) ? new File(modFile.getAbsoluteFile().getParentFile(), FileUtils.getNameWithoutExtension(modFile)) : modFile; + String description, extension = FileUtils.getExtension(file); + switch (extension) { + case "zip": + case "jar": + try { + return ForgeModMetadata.fromFile(modFile); + } catch (Exception ignore) { + description = "May be Forge mod"; + } + break; + case "litemod": + try { + return LiteModMetadata.fromFile(modFile); + } catch (Exception ignore) { + description = "May be LiteLoader mod"; + } + break; + default: + throw new IllegalArgumentException("File " + modFile + " is not a mod file."); + } + return new ModInfo(modFile, FileUtils.getNameWithoutExtension(modFile), description); + } + + public static boolean isDisabled(File file) { + return DISABLED_EXTENSION.equals(FileUtils.getExtension(file)); + } + + public static final String DISABLED_EXTENSION = "disabled"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java new file mode 100644 index 000000000..7f8463940 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -0,0 +1,79 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import org.jackhuang.hmcl.game.GameRepository; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.SimpleMultimap; +import org.jackhuang.hmcl.util.VersionNumber; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.function.Consumer; + +public final class ModManager { + private final GameRepository repository; + private final SimpleMultimap modCache = new SimpleMultimap(HashMap::new, TreeSet::new); + + public ModManager(GameRepository repository) { + this.repository = repository; + } + + public Collection refreshMods(String id) { + modCache.removeKey(id); + File modsDirectory = new File(repository.getRunDirectory(id), "mods"); + Consumer puter = modFile -> Lang.ignoringException(() -> modCache.put(id, ModInfo.fromFile(modFile))); + Optional.ofNullable(modsDirectory.listFiles()).map(Arrays::stream).ifPresent(files -> files.forEach(modFile -> { + if (modFile.isDirectory() && VersionNumber.parseVersion(modFile.getName()) != null) + Optional.ofNullable(modFile.listFiles()).map(Arrays::stream).ifPresent(x -> x.forEach(puter)); + puter.accept(modFile); + })); + return modCache.get(id); + } + + public Collection getMods(String id) { + if (!modCache.containsKey(id)) + refreshMods(id); + return modCache.get(id); + } + + public void addMod(String id, File file) throws IOException { + if (!ModInfo.isFileMod(file)) + throw new IllegalArgumentException("File " + file + " is not a valid mod file."); + + if (!modCache.containsKey(id)) + refreshMods(id); + + File modsDirectory = new File(repository.getRunDirectory(id), "mods"); + if (!FileUtils.makeDirectory(modsDirectory)) + throw new IOException("Cannot make directory " + modsDirectory); + + File newFile = new File(modsDirectory, file.getName()); + FileUtils.copyFile(file, newFile); + + modCache.put(id, ModInfo.fromFile(newFile)); + } + + public boolean removeMods(String id, ModInfo... modInfos) { + boolean result = Arrays.stream(modInfos).reduce(true, (acc, modInfo) -> acc && modInfo.getFile().delete(), Boolean::logicalAnd); + refreshMods(id); + return result; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.java new file mode 100644 index 000000000..da56c8659 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Modpack.java @@ -0,0 +1,76 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +/** + * + * @author huangyuhui + */ +public final class Modpack { + private final String name; + private final String author; + private final String version; + private final String gameVersion; + private final String description; + private final Object manifest; + + public Modpack() { + this("", null, null, null, null, null); + } + + public Modpack(String name, String author, String version, String gameVersion, String description, Object manifest) { + this.name = name; + this.author = author; + this.version = version; + this.gameVersion = gameVersion; + this.description = description; + this.manifest = manifest; + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } + + public String getVersion() { + return version; + } + + public String getGameVersion() { + return gameVersion; + } + + public Modpack setGameVersion(String gameVersion) { + return new Modpack(name, author, version, gameVersion, description, manifest); + } + + public String getDescription() { + return description; + } + + public Object getManifest() { + return manifest; + } + + public Modpack setManifest(Object manifest) { + return new Modpack(name, author, version, gameVersion, description, manifest); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCInstanceConfiguration.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCInstanceConfiguration.java new file mode 100644 index 000000000..e823ece65 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCInstanceConfiguration.java @@ -0,0 +1,266 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.StringUtils; + +/** + * + * @author huangyuhui + */ +public final class MultiMCInstanceConfiguration { + + private final String name; // name + private final String gameVersion; // IntendedVersion + private final Integer permGen; // PermGen + private final String wrapperCommand; // WrapperCommand + private final String preLaunchCommand; // PreLaunchCommand + private final String postExitCommand; // PostExitCommand + private final String notes; // notes + private final String javaPath; // JavaPath + private final String jvmArgs; // JvmArgs + private final boolean fullscreen; // LaunchMaximized + private final Integer width; // MinecraftWinWidth + private final Integer height; // MinecraftWinHeight + private final Integer maxMemory; // MaxMemAlloc + private final Integer minMemory; // MinMemAlloc + private final boolean showConsole; // ShowConsole + private final boolean showConsoleOnError; // ShowConsoleOnError + private final boolean autoCloseConsole; // AutoCloseConsole + private final boolean overrideMemory; // OverrideMemory + private final boolean overrideJavaLocation; // OverrideJavaLocation + private final boolean overrideJavaArgs; // OverrideJavaArgs + private final boolean overrideConsole; // OverrideConsole + private final boolean overrideCommands; // OverrideCommands + private final boolean overrideWindow; // OverrideWindow + + public MultiMCInstanceConfiguration(String defaultName, InputStream contentStream) throws IOException { + Properties p = new Properties(); + p.load(contentStream); + + autoCloseConsole = Boolean.parseBoolean(p.getProperty("AutoCloseConsole")); + gameVersion = p.getProperty("IntendedVersion"); + javaPath = p.getProperty("JavaPath"); + jvmArgs = p.getProperty("JvmArgs"); + fullscreen = Boolean.parseBoolean(p.getProperty("LaunchMaximized")); + maxMemory = StringUtils.parseInt(p.getProperty("MaxMemAlloc")); + minMemory = StringUtils.parseInt(p.getProperty("MinMemAlloc")); + height = StringUtils.parseInt(p.getProperty("MinecraftWinHeight")); + width = StringUtils.parseInt(p.getProperty("MinecraftWinWidth")); + overrideCommands = Boolean.parseBoolean(p.getProperty("OverrideCommands")); + overrideConsole = Boolean.parseBoolean(p.getProperty("OverrideConsole")); + overrideJavaArgs = Boolean.parseBoolean(p.getProperty("OverrideJavaArgs")); + overrideJavaLocation = Boolean.parseBoolean(p.getProperty("OverrideJavaLocation")); + overrideMemory = Boolean.parseBoolean(p.getProperty("OverrideMemory")); + overrideWindow = Boolean.parseBoolean(p.getProperty("OverrideWindow")); + permGen = StringUtils.parseInt(p.getProperty("PermGen")); + postExitCommand = p.getProperty("PostExitCommand"); + preLaunchCommand = p.getProperty("PreLaunchCommand"); + showConsole = Boolean.parseBoolean(p.getProperty("ShowConsole")); + showConsoleOnError = Boolean.parseBoolean(p.getProperty("ShowConsoleOnError")); + wrapperCommand = p.getProperty("WrapperCommand"); + name = defaultName; + notes = Lang.nonNull(p.getProperty("notes"), ""); + } + + /** + * The instance's name. + */ + public String getName() { + return name; + } + + /** + * The game version of the instance. + */ + public String getGameVersion() { + return gameVersion; + } + + /** + * The permanent generation size of JVM. + * Nullable. + */ + public Integer getPermGen() { + return permGen; + } + + /** + * The command to launch JVM. + */ + public String getWrapperCommand() { + return wrapperCommand; + } + + /** + * The command that will be executed before game launches. + */ + public String getPreLaunchCommand() { + return preLaunchCommand; + } + + /** + * The command that will be executed after game exits. + */ + public String getPostExitCommand() { + return postExitCommand; + } + + /** + * The description of the instance. + */ + public String getNotes() { + return notes; + } + + /** + * JVM installation location. + */ + public String getJavaPath() { + return javaPath; + } + + /** + * The JVM's arguments + */ + public String getJvmArgs() { + return jvmArgs; + } + + /** + * True if Minecraft will start in fullscreen mode. + */ + public boolean isFullscreen() { + return fullscreen; + } + + /** + * The initial width of the game window. + * Nullable. + */ + public Integer getWidth() { + return width; + } + + /** + * The initial height of the game window. + * Nullable. + */ + public Integer getHeight() { + return height; + } + + /** + * The maximum memory that JVM can allocate. + * Nullable. + */ + public Integer getMaxMemory() { + return maxMemory; + } + + /** + * The minimum memory that JVM can allocate. + * Nullable. + */ + public Integer getMinMemory() { + return minMemory; + } + + /** + * True if show the console window when game launches. + */ + public boolean isShowConsole() { + return showConsole; + } + + /** + * True if show the console window when game crashes. + */ + public boolean isShowConsoleOnError() { + return showConsoleOnError; + } + + /** + * True if closes the console window when game stops. + */ + public boolean isAutoCloseConsole() { + return autoCloseConsole; + } + + /** + * True if {@link #getMaxMemory}, {@link #getMinMemory}, {@link #getPermGen} will come info force. + */ + public boolean isOverrideMemory() { + return overrideMemory; + } + + /** + * True if {@link #getJavaPath} will come info force. + */ + public boolean isOverrideJavaLocation() { + return overrideJavaLocation; + } + + /** + * True if {@link #getJvmArgs} will come info force. + */ + public boolean isOverrideJavaArgs() { + return overrideJavaArgs; + } + + /** + * True if {@link #isShowConsole}, {@link #isShowConsoleOnError}, {@link #isAutoCloseConsole} will come into force. + */ + public boolean isOverrideConsole() { + return overrideConsole; + } + + /** + * True if {@link #getPreLaunchCommand}, {@link #getPostExitCommand}, {@link #getWrapperCommand} will come into force. + */ + public boolean isOverrideCommands() { + return overrideCommands; + } + + /** + * True if {@link #getHeight}, {@link #getWidth}, {@link #isFullscreen} will come into force. + */ + public boolean isOverrideWindow() { + return overrideWindow; + } + + public static Modpack readMultiMCModpackManifest(File f) throws IOException { + try (ZipFile zipFile = new ZipFile(f)) { + ZipArchiveEntry firstEntry = zipFile.getEntries().nextElement(); + String name = StringUtils.substringBefore(firstEntry.getName(), '/'); + ZipArchiveEntry entry = zipFile.getEntry(name + "/instance.cfg"); + if (entry == null) + throw new IOException("`instance.cfg` not found, " + f + " is not a valid MultiMC modpack."); + MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, zipFile.getInputStream(entry)); + return new Modpack(cfg.getName(), "", "", cfg.getGameVersion(), cfg.getNotes(), cfg); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCInstancePatch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCInstancePatch.java new file mode 100644 index 000000000..749916544 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCInstancePatch.java @@ -0,0 +1,90 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import com.google.gson.annotations.SerializedName; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.util.Immutable; + +/** + * + * @author huangyuhui + */ +@Immutable +public final class MultiMCInstancePatch { + + private final String name; + private final String version; + + @SerializedName("mcVersion") + private final String gameVersion; + private final String mainClass; + private final String fileId; + + @SerializedName("+tweakers") + private final List tweakers; + + @SerializedName("+libraries") + private final List libraries; + + public MultiMCInstancePatch() { + this("", "", "", "", "", Collections.EMPTY_LIST, Collections.EMPTY_LIST); + } + + public MultiMCInstancePatch(String name, String version, String gameVersion, String mainClass, String fileId, List tweakers, List libraries) { + this.name = name; + this.version = version; + this.gameVersion = gameVersion; + this.mainClass = mainClass; + this.fileId = fileId; + this.tweakers = new ArrayList<>(tweakers); + this.libraries = new ArrayList<>(libraries); + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } + + public String getGameVersion() { + return gameVersion; + } + + public String getMainClass() { + return mainClass; + } + + public String getFileId() { + return fileId; + } + + public List getTweakers() { + return Collections.unmodifiableList(tweakers); + } + + public List getLibraries() { + return Collections.unmodifiableList(libraries); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java new file mode 100644 index 000000000..b58f81baa --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java @@ -0,0 +1,105 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.game.DefaultGameRepository; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.CompressingUtils; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.IOUtils; +import org.jackhuang.hmcl.util.Lang; + +/** + * + * @author huangyuhui + */ +public final class MultiMCModpackInstallTask extends Task { + + private final DefaultDependencyManager dependencyManager; + private final File zipFile; + private final MultiMCInstanceConfiguration manifest; + private final String name; + private final File run; + private final DefaultGameRepository repository; + private final List dependencies = new LinkedList<>(); + private final List dependents = new LinkedList<>(); + + public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, File zipFile, MultiMCInstanceConfiguration manifest, String name) { + this.dependencyManager = dependencyManager; + this.zipFile = zipFile; + this.manifest = manifest; + this.name = name; + this.repository = dependencyManager.getGameRepository(); + this.run = repository.getRunDirectory(name); + + if (repository.hasVersion(name)) + throw new IllegalArgumentException("Version " + name + " already exists."); + dependents.add(dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion()).buildAsync()); + onDone().register(event -> { + if (event.isFailed()) + repository.removeVersionFromDisk(name); + }); + } + + @Override + public List getDependencies() { + return dependencies; + } + + @Override + public List getDependents() { + return dependents; + } + + @Override + public void execute() throws Exception { + Version version = Objects.requireNonNull(repository.readVersionJson(name)); + CompressingUtils.unzip(zipFile, run, manifest.getName() + "/minecraft/", null, false, true); + + try (ZipFile zip = new ZipFile(zipFile)) { + for (ZipArchiveEntry entry : Lang.asIterable(zip.getEntries())) { + // ensure that this entry is in folder 'patches' and is a json file. + if (!entry.isDirectory() && entry.getName().startsWith(manifest.getName() + "/patches/") && entry.getName().endsWith(".json")) { + MultiMCInstancePatch patch = Constants.GSON.fromJson(IOUtils.readFullyAsString(zip.getInputStream(entry)), MultiMCInstancePatch.class); + List newArguments = new LinkedList<>(); + for (String arg : patch.getTweakers()) { + newArguments.add("--tweakClass"); + newArguments.add(arg); + } + version = version + .setLibraries(Lang.merge(version.getLibraries(), patch.getLibraries())) + .setMainClass(patch.getMainClass()) + .setArguments(Arguments.addGameArguments(version.getArguments().orElse(null), newArguments)); + } + } + } + + dependencies.add(new VersionJsonSaveTask(repository, version)); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.java new file mode 100644 index 000000000..c2e6971fa --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.java @@ -0,0 +1,78 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; +import org.jackhuang.hmcl.util.AutoTypingMap; + +/** + * A task that combines two tasks and make sure [pred] runs before succ. + * + * @author huangyuhui + */ +final class CoupleTask

extends Task { + + private final boolean relyingOnDependents; + private final Collection dependents; + private final List dependencies = new LinkedList<>(); + private final Function, Task> succ; + + /** + * A task that combines two tasks and make sure pred runs before succ. + * + * @param pred the task that runs before succ. + * @param succ a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred. + * @param relyingOnDependents true if this task chain will be broken when task pred fails. + */ + public CoupleTask(P pred, Function, Task> succ, boolean relyingOnDependents) { + this.dependents = Collections.singleton(pred); + this.succ = succ; + this.relyingOnDependents = relyingOnDependents; + } + + @Override + public void execute() throws Exception { + Task task = succ.apply(getVariables()); + if (task != null) + dependencies.add(task); + } + + @Override + public boolean isHidden() { + return true; + } + + @Override + public Collection getDependents() { + return dependents; + } + + @Override + public Collection getDependencies() { + return dependencies; + } + + @Override + public boolean isRelyingOnDependents() { + return relyingOnDependents; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java new file mode 100644 index 000000000..c25584a4b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java @@ -0,0 +1,221 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.math.BigInteger; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.URL; +import java.security.MessageDigest; +import java.util.logging.Level; +import org.jackhuang.hmcl.event.EventManager; +import org.jackhuang.hmcl.event.FailedEvent; +import org.jackhuang.hmcl.util.DigestUtils; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.IOUtils; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * A task that can download a file online. + * + * @author huangyuhui + */ +public class FileDownloadTask extends Task { + + private final URL url; + private final File file; + private final String hash; + private final int retry; + private final Proxy proxy; + private final EventManager> onFailed = new EventManager<>(); + private RandomAccessFile rFile; + private InputStream stream; + + /** + * @param url the URL of remote file. + * @param file the location that download to. + */ + public FileDownloadTask(URL url, File file) { + this(url, file, Proxy.NO_PROXY); + } + + /** + * @param url the URL of remote file. + * @param file the location that download to. + * @param proxy the proxy. + */ + public FileDownloadTask(URL url, File file, Proxy proxy) { + this(url, file, proxy, null); + } + + /** + * @param url the URL of remote file. + * @param file the location that download to. + * @param proxy the proxy. + * @param hash the SHA-1 hash code of remote file, null if the hash is unknown or it is no need to check the hash code. + */ + public FileDownloadTask(URL url, File file, Proxy proxy, String hash) { + this(url, file, proxy, hash, 5); + } + + /** + * @param url the URL of remote file. + * @param file the location that download to. + * @param hash the SHA-1 hash code of remote file, null if the hash is unknown or it is no need to check the hash code. + * @param retry the times for retrying if downloading fails. + * @param proxy the proxy. + */ + public FileDownloadTask(URL url, File file, Proxy proxy, String hash, int retry) { + this.url = url; + this.file = file; + this.hash = hash; + this.retry = retry; + this.proxy = proxy; + } + + private void closeFiles() { + IOUtils.closeQuietly(rFile); + rFile = null; + IOUtils.closeQuietly(stream); + stream = null; + } + + public EventManager> getOnFailed() { + return onFailed; + } + + @Override + public void execute() throws Exception { + URL currentURL = url; + Logging.LOG.log(Level.FINER, "Downloading {0}, to {1}", new Object[] { currentURL, file }); + Exception exception = null; + + for (int repeat = 0; repeat < retry; repeat++) { + if (repeat > 0) { + FailedEvent event = new FailedEvent<>(this, repeat, currentURL); + onFailed.fireEvent(event); + if (!currentURL.equals(event.getNewResult())) { + Logging.LOG.log(Level.FINE, "Switch from {0} to {1}", new Object[] { currentURL, event.getNewResult() }); + currentURL = event.getNewResult(); + } + } + if (Thread.interrupted()) { + Thread.currentThread().interrupt(); + break; + } + + File temp = null; + + try { + updateProgress(0); + + HttpURLConnection con = NetworkUtils.createConnection(url, proxy); + con.connect(); + + if (con.getResponseCode() / 100 != 2) + throw new IOException("Server error, response code: " + con.getResponseCode()); + + int contentLength = con.getContentLength(); + if (contentLength < 1) + throw new IOException("The content length is invalid."); + + if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) + throw new IOException("Could not make directory " + file.getAbsoluteFile().getParent()); + + temp = FileUtils.createTempFile(); + rFile = new RandomAccessFile(temp, "rw"); + + MessageDigest digest = DigestUtils.getSha1Digest(); + + stream = con.getInputStream(); + int lastDownloaded = 0, downloaded = 0; + long lastTime = System.currentTimeMillis(); + byte buffer[] = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; + while (true) { + if (Thread.interrupted()) { + Thread.currentThread().interrupt(); + break; + } + + int read = stream.read(buffer); + if (read == -1) + break; + + if (hash != null) + digest.update(buffer, 0, read); + + // Write buffer to file. + rFile.write(buffer, 0, read); + downloaded += read; + + // Update progress information per second + updateProgress(downloaded, contentLength); + long now = System.currentTimeMillis(); + if (now - lastTime >= 1000) { + updateMessage((downloaded - lastDownloaded) / 1024 + "KB/s"); + lastDownloaded = downloaded; + lastTime = now; + } + } + + closeFiles(); + + // Restore temp file to original name. + if (Thread.interrupted()) { + temp.delete(); + Thread.currentThread().interrupt(); + break; + } else { + if (file.exists()) + file.delete(); + if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) + throw new IOException("Cannot make parent directory " + file); + if (!temp.renameTo(file)) + throw new IOException("Cannot move temp file to " + file); + } + + if (downloaded != contentLength) + throw new IllegalStateException("Unexptected file size: " + downloaded + ", expected: " + contentLength); + + // Check hash code + if (hash != null) { + String hashCode = String.format("%1$040x", new BigInteger(1, digest.digest())); + if (!hash.equalsIgnoreCase(hashCode)) + throw new IllegalStateException("Unexpected hash code: " + hashCode + ", expected: " + hash); + } + + return; + } catch (IOException | IllegalStateException e) { + if (temp != null) + temp.delete(); + Logging.LOG.log(Level.WARNING, "Unable to download file " + currentURL, e); + } finally { + closeFiles(); + } + } + + if (exception != null) + throw exception; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.java new file mode 100644 index 000000000..821070701 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.java @@ -0,0 +1,119 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.logging.Level; +import org.jackhuang.hmcl.util.Charsets; +import org.jackhuang.hmcl.util.IOUtils; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.NetworkUtils; + +/** + * + * @author huang + */ +public final class GetTask extends TaskResult { + + private final URL url; + private final Charset charset; + private final int retry; + private final Proxy proxy; + private final String id; + + public GetTask(URL url) { + this(url, Proxy.NO_PROXY); + } + + public GetTask(URL url, Proxy proxy) { + this(url, proxy, ID); + } + + public GetTask(URL url, Proxy proxy, String id) { + this(url, proxy, id, Charsets.DEFAULT_CHARSET); + } + + public GetTask(URL url, Proxy proxy, String id, Charset charset) { + this(url, proxy, id, charset, 5); + } + + public GetTask(URL url, Proxy proxy, String id, Charset charset, int retry) { + this.url = url; + this.charset = charset; + this.retry = retry; + this.proxy = proxy; + this.id = id; + } + + @Override + public Scheduler getScheduler() { + return Schedulers.io(); + } + + @Override + public String getId() { + return id; + } + + @Override + public void execute() throws Exception { + Exception exception = null; + for (int time = 0; time < retry; ++time) { + if (time > 0) + Logging.LOG.log(Level.WARNING, "Failed to download, repeat times: {0}", time); + try { + updateProgress(0); + HttpURLConnection conn = NetworkUtils.createConnection(url, proxy); + InputStream input = conn.getInputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; + int size = conn.getContentLength(), read = 0, len; + while ((len = input.read(buf)) != -1) { + baos.write(buf, 0, len); + read += len; + updateProgress(read, size); + + if (Thread.currentThread().isInterrupted()) + return; + } + + if (size > 0 && size != read) + throw new IllegalStateException("Not completed! Readed: " + read + ", total size: " + size); + + setResult(baos.toString(charset.name())); + return; + } catch (IOException ex) { + exception = ex; + } + } + if (exception != null) + throw exception; + } + + /** + * The default task result ID. + */ + public static final String ID = "http_get"; + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/ParallelTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/ParallelTask.java new file mode 100644 index 000000000..63d04d8ff --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/ParallelTask.java @@ -0,0 +1,56 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.Arrays; +import java.util.Collection; + +/** + * The tasks that provides a way to execute tasks parallelly. + * Fails when some of {@link #tasks} failed. + * + * @author huangyuhui + */ +public final class ParallelTask extends Task { + + private final Task[] tasks; + + /** + * Constructor. + * + * @param tasks the tasks that can be executed parallelly. + */ + public ParallelTask(Task... tasks) { + this.tasks = tasks; + } + + @Override + public void execute() throws Exception { + } + + @Override + public boolean isHidden() { + return true; + } + + @Override + public Collection getDependents() { + return Arrays.asList(tasks); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.java new file mode 100644 index 000000000..7c6bbe432 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.java @@ -0,0 +1,37 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.concurrent.Future; +import org.jackhuang.hmcl.util.ExceptionalRunnable; + +/** + * Determines how a task is executed. + * + * @author huangyuhui + */ +public abstract class Scheduler { + + /** + * Schedules the given task. + * + * @return the future + */ + public abstract Future schedule(ExceptionalRunnable block); + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SchedulerExecutorService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SchedulerExecutorService.java new file mode 100644 index 000000000..8dc07b914 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SchedulerExecutorService.java @@ -0,0 +1,41 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import org.jackhuang.hmcl.util.ExceptionalRunnable; + +/** + * + * @author huangyuhui + */ +class SchedulerExecutorService extends Scheduler { + + private final ExecutorService executorService; + + public SchedulerExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + @Override + public Future schedule(ExceptionalRunnable block) { + return executorService.submit(block.toCallable()); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SchedulerImpl.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SchedulerImpl.java new file mode 100644 index 000000000..1a0f9374c --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SchedulerImpl.java @@ -0,0 +1,93 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import org.jackhuang.hmcl.util.ExceptionalRunnable; + +/** + * + * @author huangyuhui + */ +class SchedulerImpl extends Scheduler { + + private final Consumer executor; + + public SchedulerImpl(Consumer executor) { + this.executor = executor; + } + + @Override + public Future schedule(ExceptionalRunnable block) { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference wrapper = new AtomicReference<>(); + + executor.accept(() -> { + try { + block.run(); + } catch (Exception e) { + wrapper.set(e); + } finally { + latch.countDown(); + } + }); + + return new Future() { + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return latch.getCount() == 0; + } + + private Void getImpl() throws ExecutionException { + Exception e = wrapper.get(); + if (e != null) + throw new ExecutionException(e); + return null; + } + + @Override + public Void get() throws InterruptedException, ExecutionException { + latch.await(); + return getImpl(); + } + + @Override + public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + latch.await(timeout, unit); + return getImpl(); + } + }; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java new file mode 100644 index 000000000..9a4ac7921 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Schedulers.java @@ -0,0 +1,127 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * + * @author huang + */ +public final class Schedulers { + + private Schedulers() { + } + + private static volatile ExecutorService CACHED_EXECUTOR; + + private static synchronized ExecutorService getCachedExecutorService() { + if (CACHED_EXECUTOR == null) + CACHED_EXECUTOR = new ThreadPoolExecutor(0, Integer.MAX_VALUE, + 60, TimeUnit.SECONDS, new SynchronousQueue<>()); + + return CACHED_EXECUTOR; + } + + private static volatile ExecutorService IO_EXECUTOR; + + private static synchronized ExecutorService getIOExecutorService() { + if (IO_EXECUTOR == null) + IO_EXECUTOR = Executors.newFixedThreadPool(6, r -> { + Thread thread = Executors.defaultThreadFactory().newThread(r); + thread.setDaemon(true); + return thread; + }); + + return IO_EXECUTOR; + } + + private static volatile ExecutorService SINGLE_EXECUTOR; + + private static synchronized ExecutorService getSingleExecutorService() { + if (SINGLE_EXECUTOR == null) + SINGLE_EXECUTOR = Executors.newSingleThreadExecutor(r -> { + Thread thread = Executors.defaultThreadFactory().newThread(r); + thread.setDaemon(true); + return thread; + }); + + return SINGLE_EXECUTOR; + } + + private static final Scheduler IMMEDIATE = new SchedulerImpl(Runnable::run); + + public static Scheduler immediate() { + return IMMEDIATE; + } + + private static Scheduler NEW_THREAD; + + public static synchronized Scheduler newThread() { + if (NEW_THREAD == null) + NEW_THREAD = new SchedulerExecutorService(getCachedExecutorService()); + return NEW_THREAD; + } + + private static Scheduler IO; + + public static synchronized Scheduler io() { + if (IO == null) + IO = new SchedulerExecutorService(getIOExecutorService()); + return IO; + } + + private static Scheduler COMPUTATION; + + public static synchronized Scheduler computation() { + if (COMPUTATION == null) + COMPUTATION = new SchedulerExecutorService(getSingleExecutorService()); + return COMPUTATION; + } + + private static final Scheduler JAVAFX = new SchedulerImpl(javafx.application.Platform::runLater); + + public static Scheduler javafx() { + return JAVAFX; + } + + private static final Scheduler SWING = new SchedulerImpl(javax.swing.SwingUtilities::invokeLater); + + public static Scheduler swing() { + return SWING; + } + + public static synchronized Scheduler defaultScheduler() { + return newThread(); + } + + public static synchronized void shutdown() { + if (CACHED_EXECUTOR != null) + CACHED_EXECUTOR.shutdown(); + + if (IO_EXECUTOR != null) + IO_EXECUTOR.shutdown(); + + if (SINGLE_EXECUTOR != null) + SINGLE_EXECUTOR.shutdown(); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Annotation.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SilentException.java similarity index 76% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Annotation.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/task/SilentException.java index ffe932f8e..753407857 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Annotation.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SilentException.java @@ -15,11 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.util +package org.jackhuang.hmcl.task; /** - * Mark if the model is immutable. + * If a task throws {@link SilentException}, + * the task will be marked as failure but do not log the stacktrace. + * + * @author huangyuhui */ -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.SOURCE) -annotation class Immutable \ No newline at end of file +public final class SilentException extends Exception { + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SimpleTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SimpleTask.java new file mode 100644 index 000000000..618b1b777 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/SimpleTask.java @@ -0,0 +1,50 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.function.Consumer; +import org.jackhuang.hmcl.util.AutoTypingMap; + +/** + * + * @author huangyuhui + */ +class SimpleTask extends Task { + + private final Consumer> consumer; + private final Scheduler scheduler; + + public SimpleTask(Consumer> consumer) { + this(consumer, Schedulers.defaultScheduler()); + } + + public SimpleTask(Consumer> consumer, Scheduler scheduler) { + this.consumer = consumer; + this.scheduler = scheduler; + } + + @Override + public Scheduler getScheduler() { + return scheduler; + } + + @Override + public void execute() throws Exception { + consumer.accept(getVariables()); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java new file mode 100644 index 000000000..18b502d31 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java @@ -0,0 +1,250 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyDoubleWrapper; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.ReadOnlyStringWrapper; +import org.jackhuang.hmcl.util.AutoTypingMap; +import org.jackhuang.hmcl.event.EventManager; +import org.jackhuang.hmcl.util.Properties; + +/** + * Disposable task. + * + * @author huangyuhui + */ +public abstract class Task { + + private final EventManager onDone = new EventManager<>(); + + /** + * True if not logging when executing this task. + */ + public boolean isHidden() { + return false; + } + + /** + * The scheduler that decides how this task runs. + */ + public Scheduler getScheduler() { + return Schedulers.defaultScheduler(); + } + + /** + * True if requires all {@link #getDependents} finishing successfully. + * + * **Note** if this field is set false, you are not supposed to invoke [run] + * + * @defaultValue true + */ + public boolean isRelyingOnDependents() { + return true; + } + + /** + * True if requires all {@link #getDependencies} finishing successfully. + * + * **Note** if this field is set false, you are not supposed to invoke [run] + * + * @defaultValue false + */ + public boolean isRelyingOnDependencies() { + return true; + } + + private String name = getClass().toString(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private AutoTypingMap variables = null; + + public AutoTypingMap getVariables() { + return variables; + } + + void setVariables(AutoTypingMap variables) { + this.variables = variables; + } + + /** + * @see Thread#isInterrupted + * @throws InterruptedException if current thread is interrupted + */ + public abstract void execute() throws Exception; + + /** + * The collection of sub-tasks that should execute **before** this task running. + */ + public Collection getDependents() { + return Collections.EMPTY_SET; + } + + /** + * The collection of sub-tasks that should execute **after** this task running. + */ + public Collection getDependencies() { + return Collections.EMPTY_SET; + } + + public EventManager onDone() { + return onDone; + } + + protected long getProgressInterval() { + return 1000L; + } + + private long lastTime = Long.MIN_VALUE; + private final AtomicReference progressUpdate = new AtomicReference<>(); + private final ReadOnlyDoubleWrapper progressProperty = new ReadOnlyDoubleWrapper(this, "progress", 0); + + public ReadOnlyDoubleProperty getProgressProperty() { + return progressProperty.getReadOnlyProperty(); + } + + protected void updateProgress(int progress, int total) { + updateProgress(1.0 * progress / total); + } + + protected void updateProgress(double progress) { + if (progress > 1.0) + throw new IllegalArgumentException("Progress is must between 0 and 1."); + long now = System.currentTimeMillis(); + if (now - lastTime >= getProgressInterval()) { + updateProgressImmediately(progress); + lastTime = now; + } + } + + protected void updateProgressImmediately(double progress) { + Properties.updateAsync(progressProperty, progress, progressUpdate); + } + + private final AtomicReference messageUpdate = new AtomicReference<>(); + private final ReadOnlyStringWrapper messageProperty = new ReadOnlyStringWrapper(this, "message", null); + + public final ReadOnlyStringProperty getMessageProperty() { + return messageProperty.getReadOnlyProperty(); + } + + protected final void updateMessage(String newMessage) { + Properties.updateAsync(messageProperty, newMessage, messageUpdate); + } + + public final void run() throws Exception { + for (Task task : getDependents()) + doSubTask(task); + execute(); + for (Task task : getDependencies()) + doSubTask(task); + onDone.fireEvent(new TaskEvent(this, this, false)); + } + + private void doSubTask(Task task) throws Exception { + messageProperty.bind(task.messageProperty); + progressProperty.bind(task.progressProperty); + task.run(); + messageProperty.unbind(); + progressProperty.unbind(); + } + + ; + + public final TaskExecutor executor() { + return new TaskExecutor(this); + } + + public final TaskExecutor executor(TaskListener taskListener) { + TaskExecutor executor = new TaskExecutor(this); + executor.setTaskListener(taskListener); + return executor; + } + + public final void start() { + executor().start(); + } + + public final boolean test() throws Exception { + return executor().test(); + } + + public final TaskExecutor subscribe(Task subscriber) { + TaskExecutor executor = new TaskExecutor(with(subscriber)); + executor.start(); + return executor; + } + + public final TaskExecutor subscribe(Scheduler scheduler, Consumer> closure) { + return subscribe(of(closure, scheduler)); + } + + public final TaskExecutor subscribe(Consumer> closure) { + return subscribe(of(closure)); + } + + public final Task then(Task b) { + return then(s -> b); + } + + public final Task then(Function, Task> b) { + return new CoupleTask<>(this, b, true); + } + + public final Task with(Task b) { + return with(s -> b); + } + + public final Task with(Function, Task> b) { + return new CoupleTask<>(this, b, false); + } + + public static Task of(Runnable runnable) { + return of(s -> runnable.run()); + } + + public static Task of(Consumer> closure) { + return of(closure, Schedulers.defaultScheduler()); + } + + public static Task of(Consumer> closure, Scheduler scheduler) { + return new SimpleTask(closure, scheduler); + } + + public static TaskResult ofResult(String id, Callable callable) { + return new TaskCallable<>(id, callable); + } + + public static TaskResult ofResult(String id, Function, V> closure) { + return new TaskCallable2<>(id, closure); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskCallable.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskCallable.java new file mode 100644 index 000000000..13986c29f --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskCallable.java @@ -0,0 +1,45 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.concurrent.Callable; + +/** + * + * @author huangyuhui + */ +class TaskCallable extends TaskResult { + + private final String id; + private final Callable callable; + + public TaskCallable(String id, Callable callable) { + this.id = id; + this.callable = callable; + } + + @Override + public String getId() { + return id; + } + + @Override + public void execute() throws Exception { + setResult(callable.call()); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskCallable2.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskCallable2.java new file mode 100644 index 000000000..f81179420 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskCallable2.java @@ -0,0 +1,46 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.function.Function; +import org.jackhuang.hmcl.util.AutoTypingMap; + +/** + * + * @author huangyuhui + */ +class TaskCallable2 extends TaskResult { + + private final String id; + private final Function, V> callable; + + public TaskCallable2(String id, Function, V> callable) { + this.id = id; + this.callable = callable; + } + + @Override + public String getId() { + return id; + } + + @Override + public void execute() throws Exception { + setResult(callable.apply(getVariables())); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/SimpleVersionProvider.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskEvent.java similarity index 62% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/SimpleVersionProvider.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskEvent.java index 16fb3791a..f4ed102dc 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/SimpleVersionProvider.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskEvent.java @@ -15,20 +15,31 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.game +package org.jackhuang.hmcl.task; -open class SimpleVersionProvider() : VersionProvider { - protected val versionMap = HashMap() +import java.util.EventObject; - override fun getVersion(id: String): Version { - return versionMap[id] ?: throw VersionNotFoundException("Version id $id not found") +/** + * + * @author huang + */ +public class TaskEvent extends EventObject { + + private final Task task; + private final boolean failed; + + public TaskEvent(Object source, Task task, boolean failed) { + super(source); + this.task = task; + this.failed = failed; } - override fun hasVersion(id: String): Boolean { - return versionMap.containsKey(id) + public Task getTask() { + return task; } - fun addVersion(version: Version) { - versionMap[version.id] = version + public boolean isFailed() { + return failed; } -} \ No newline at end of file + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java new file mode 100644 index 000000000..67d655170 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java @@ -0,0 +1,224 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Objects; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import org.jackhuang.hmcl.util.AutoTypingMap; +import org.jackhuang.hmcl.util.ExceptionalRunnable; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Logging; + +/** + * + * @author huangyuhui + */ +public final class TaskExecutor { + + private final Task firstTask; + private TaskListener taskListener = TaskListener.DefaultTaskListener.INSTANCE; + private boolean canceled = false; + private Exception lastException; + private final AtomicInteger totTask = new AtomicInteger(0); + private final ConcurrentLinkedQueue> workerQueue = new ConcurrentLinkedQueue<>(); + private final AutoTypingMap variables = new AutoTypingMap<>(new HashMap<>()); + private Scheduler scheduler = Schedulers.newThread(); + + public TaskExecutor(Task task) { + this.firstTask = task; + } + + public TaskListener getTaskListener() { + return taskListener; + } + + public void setTaskListener(TaskListener taskListener) { + if (taskListener == null) + this.taskListener = TaskListener.DefaultTaskListener.INSTANCE; + else + this.taskListener = taskListener; + } + + public boolean isCanceled() { + return canceled; + } + + public Exception getLastException() { + return lastException; + } + + public void setScheduler(Scheduler scheduler) { + this.scheduler = Objects.requireNonNull(scheduler); + } + + public void start() { + workerQueue.add(scheduler.schedule(() -> { + if (!executeTasks(Collections.singleton(firstTask))) + taskListener.onTerminate(); + })); + } + + public boolean test() { + AtomicBoolean flag = new AtomicBoolean(true); + Future future = scheduler.schedule(() -> { + if (!executeTasks(Collections.singleton(firstTask))) { + taskListener.onTerminate(); + flag.set(false); + } + }); + workerQueue.add(future); + Lang.invoke(() -> future.get()); + return flag.get(); + } + + /** + * Cancel the subscription ant interrupt all tasks. + */ + public synchronized void cancel() { + canceled = true; + + while (!workerQueue.isEmpty()) { + Future future = workerQueue.poll(); + if (future != null) + future.cancel(true); + } + } + + private boolean executeTasks(Collection tasks) { + if (tasks.isEmpty()) + return true; + + totTask.addAndGet(tasks.size()); + AtomicBoolean success = new AtomicBoolean(true); + CountDownLatch latch = new CountDownLatch(tasks.size()); + for (Task task : tasks) { + if (canceled) + return false; + Invoker invoker = new Invoker(task, latch, success); + Future future = task.getScheduler().schedule(invoker); + if (future != null) + workerQueue.add(future); + } + + if (canceled) + return false; + + try { + latch.await(); + return success.get() && !canceled; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + // Once interrupted, we are aborting the subscription. + // and operations fail. + return false; + } + } + + private boolean executeTask(Task task) { + if (canceled) + return false; + + if (!task.isHidden()) + Logging.LOG.log(Level.FINE, "Executing task: {0}", task.getName()); + + taskListener.onReady(task); + + boolean doDependentsSucceeded = executeTasks(task.getDependents()); + boolean flag = false; + + try { + if (!doDependentsSucceeded && task.isRelyingOnDependents() || canceled) + throw new SilentException(); + + task.setVariables(variables); + task.execute(); + + if (task instanceof TaskResult) { + TaskResult taskResult = (TaskResult) task; + variables.set(taskResult.getId(), taskResult.getResult()); + } + + if (!executeTasks(task.getDependencies()) && task.isRelyingOnDependencies()) + throw new IllegalStateException("Subtasks failed for " + task.getName()); + + flag = true; + if (!task.isHidden()) { + Logging.LOG.log(Level.FINER, "Task finished: {0}", task.getName()); + + task.onDone().fireEvent(new TaskEvent(this, task, false)); + taskListener.onFinished(task); + } + } catch (InterruptedException e) { + if (!task.isHidden()) { + lastException = e; + Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName(), e); + task.onDone().fireEvent(new TaskEvent(this, task, true)); + taskListener.onFailed(task, e); + } + } catch (SilentException e) { + // do nothing + } catch (Exception e) { + if (!task.isHidden()) { + lastException = e; + Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e); + task.onDone().fireEvent(new TaskEvent(this, task, true)); + taskListener.onFailed(task, e); + } + } finally { + task.setVariables(null); + } + return flag; + } + + public int getRunningTasks() { + return totTask.get(); + } + + private class Invoker implements ExceptionalRunnable { + + private final Task task; + private final CountDownLatch latch; + private final AtomicBoolean success; + + public Invoker(Task task, CountDownLatch latch, AtomicBoolean success) { + this.task = task; + this.latch = latch; + this.success = success; + } + + @Override + public void run() throws Exception { + try { + Thread.currentThread().setName(task.getName()); + if (!executeTask(task)) + success.set(false); + } finally { + latch.countDown(); + } + } + + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java new file mode 100644 index 000000000..ecceaebca --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskListener.java @@ -0,0 +1,44 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.task; + +import java.util.EventListener; + +/** + * + * @author huangyuhui + */ +public abstract class TaskListener implements EventListener { + + public void onReady(Task task) { + } + + public void onFinished(Task task) { + } + + public void onFailed(Task task, Throwable throwable) { + } + + public void onTerminate() { + } + + public static class DefaultTaskListener extends TaskListener { + + public static final DefaultTaskListener INSTANCE = new DefaultTaskListener(); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/CrashReport.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskResult.java similarity index 70% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/CrashReport.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskResult.java index aeaff77fd..8533e2352 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/CrashReport.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskResult.java @@ -15,17 +15,24 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.launch +package org.jackhuang.hmcl.task; -fun parseCrashReport(lines: List) { - var errorText: String? = null - for (line in lines) { - errorText = line.substringAfterLast("#@!@#") - if (errorText.isNotBlank()) - break +/** + * A task that has a result. + * + * @author huangyuhui + */ +public abstract class TaskResult extends Task { + + private V result; + + public V getResult() { + return result; } - if (errorText != null && errorText.isNotBlank()) { - + public void setResult(V result) { + this.result = result; } -} \ No newline at end of file + + public abstract String getId(); +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/AutoTypingMap.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/AutoTypingMap.java new file mode 100644 index 000000000..767678993 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/AutoTypingMap.java @@ -0,0 +1,61 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A map that support auto casting. + * + * @author huangyuhui + */ +public final class AutoTypingMap { + + private final Map impl; + + public AutoTypingMap(Map impl) { + this.impl = impl; + } + + public V get(K key) { + return (V) impl.get(key); + } + + public void set(K key, Object value) { + if (value != null) + impl.put(key, value); + } + + public Collection values() { + return impl.values(); + } + + public Set keys() { + return impl.keySet(); + } + + public boolean containsKey(K key) { + return impl.containsKey(key); + } + + public Object remove(K key) { + return impl.remove(key); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Charsets.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Charsets.java new file mode 100644 index 000000000..a45bec8cb --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Charsets.java @@ -0,0 +1,55 @@ +/* + * Hello Minecraft!. + * Copyright (C) 2013 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; + +public final class Charsets { + + private Charsets() { + } + + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + + public static final Charset UTF_16 = Charset.forName("UTF-16"); + + public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); + + public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); + + public static final Charset UTF_8 = Charset.forName("UTF-8"); + + public static final Charset DEFAULT_CHARSET = UTF_8; + + public static Charset toCharset(String charset) { + if (charset == null) + return Charset.defaultCharset(); + try { + return Charset.forName(charset); + } catch (UnsupportedCharsetException ignored) { + return Charset.defaultCharset(); + } + } + + public static Charset toCharset() { + return toCharset(System.getProperty("sun.jnu.encoding", Constants.DEFAULT_ENCODING)); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CompressingUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CompressingUtils.java new file mode 100644 index 000000000..e262e6dc8 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CompressingUtils.java @@ -0,0 +1,237 @@ +/* + * Hello Minecraft!. + * Copyright (C) 2013 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; +import org.apache.commons.compress.archivers.zip.ZipFile; + +/** + * 文件压缩/解压类 + * + * @author huangyuhui + */ +public final class CompressingUtils { + + private CompressingUtils() { + } + + /** + * Compress the given directory to a zip file. + * + * @param src the source directory or a file. + * @param destZip the location of dest zip file. + * @param pathNameCallback callback(pathName, isDirectory) returns your modified pathName + * @throws IOException + */ + public static void zip(File sourceDir, File zipFile, BiFunction pathNameCallback) throws IOException { + try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(new FileOutputStream(zipFile))) { + String basePath; + if (sourceDir.isDirectory()) + basePath = sourceDir.getPath(); + else + basePath = sourceDir.getParent(); + zipFile(sourceDir, basePath, zos, pathNameCallback); + zos.closeArchiveEntry(); + } + } + + /** + * Zip file. + * + * @param src source directory to be compressed. + * @param basePath the file directory to be compressed, if [src] is a file, this is the parent directory of [src] + * @param zos the [ZipOutputStream] of dest zip file. + * @param pathNameCallback callback(pathName, isDirectory) returns your modified pathName, null if you dont want this file zipped + * @throws IOException + */ + private static void zipFile(File src, String basePath, + ZipArchiveOutputStream zos, BiFunction pathNameCallback) throws IOException { + File[] files = src.isDirectory() ? src.listFiles() : new File[] { src }; + String pathName;//存相对路径(相对于待压缩的根目录) + byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; + for (File file : files) + if (file.isDirectory()) { + pathName = file.getPath().substring(basePath.length() + 1) + "/"; + if (pathNameCallback != null) + pathName = pathNameCallback.apply(pathName, true); + if (pathName == null) + continue; + zos.putArchiveEntry(new ZipArchiveEntry(pathName)); + zipFile(file, basePath, zos, pathNameCallback); + } else { + pathName = file.getPath().substring(basePath.length() + 1); + if (pathNameCallback != null) + pathName = pathNameCallback.apply(pathName, true); + if (pathName == null) + continue; + try (InputStream is = new FileInputStream(file)) { + zos.putArchiveEntry(new ZipArchiveEntry(pathName)); + IOUtils.copyTo(is, zos, buf); + } + } + } + + /** + * Decompress the given zip file to a directory. + * + * @param src the input zip file. + * @param dest the dest directory. + * @throws IOException + */ + public static void unzip(File src, File dest) throws IOException { + unzip(src, dest, ""); + } + + /** + * Decompress the given zip file to a directory. + * + * @param src the input zip file. + * @param dest the dest directory. + * @param subDirectory the subdirectory of the zip file to be decompressed. + * @throws IOException + */ + public static void unzip(File src, File dest, String subDirectory) throws IOException { + unzip(src, dest, subDirectory, null); + } + + /** + * Decompress the given zip file to a directory. + * + * @param src the input zip file. + * @param dest the dest directory. + * @param subDirectory the subdirectory of the zip file to be decompressed. + * @param callback will be called for every entry in the zip file, returns false if you dont want this file being uncompressed. + * @throws IOException + */ + public static void unzip(File src, File dest, String subDirectory, Predicate callback) throws IOException { + unzip(src, dest, subDirectory, callback, true); + } + + /** + * Decompress the given zip file to a directory. + * + * @param src the input zip file. + * @param dest the dest directory. + * @param subDirectory the subdirectory of the zip file to be decompressed. + * @param callback will be called for every entry in the zip file, returns false if you dont want this file being uncompressed. + * @param ignoreExistentFile true if skip all existent files. + * @throws IOException + */ + public static void unzip(File src, File dest, String subDirectory, Predicate callback, boolean ignoreExistentFile) throws IOException { + unzip(src, dest, subDirectory, callback, ignoreExistentFile, false); + } + + /** + * Decompress the given zip file to a directory. + * + * @param src the input zip file. + * @param dest the dest directory. + * @param subDirectory the subdirectory of the zip file to be decompressed. + * @param callback will be called for every entry in the zip file, returns false if you dont want this file being uncompressed. + * @param ignoreExistentFile true if skip all existent files. + * @param allowStoredEntriesWithDataDescriptor whether the zip stream will try to read STORED entries that use a data descriptor + * @throws IOException + */ + public static void unzip(File src, File dest, String subDirectory, Predicate callback, boolean ignoreExistentFile, boolean allowStoredEntriesWithDataDescriptor) throws IOException { + byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; + dest.mkdirs(); + try (ZipArchiveInputStream zipFile = new ZipArchiveInputStream(new FileInputStream(src), null, true, allowStoredEntriesWithDataDescriptor)) { + if (src.exists()) { + String strPath, gbkPath, strtemp; + strPath = dest.getAbsolutePath(); + ArchiveEntry zipEnt; + while ((zipEnt = zipFile.getNextEntry()) != null) { + gbkPath = zipEnt.getName(); + + if (!gbkPath.startsWith(subDirectory)) + continue; + gbkPath = gbkPath.substring(subDirectory.length()); + if (gbkPath.startsWith("/") || gbkPath.startsWith("\\")) + gbkPath = gbkPath.substring(1); + strtemp = strPath + File.separator + gbkPath; + + if (callback != null) + if (!callback.test(gbkPath)) + continue; + + if (zipEnt.isDirectory()) { + File dir = new File(strtemp); + dir.mkdirs(); + } else { + //建目录 + String strsubdir = gbkPath; + for (int i = 0; i < strsubdir.length(); i++) + if (strsubdir.substring(i, i + 1).equalsIgnoreCase("/")) { + String temp = strPath + File.separator + strsubdir.substring(0, i); + File subdir = new File(temp); + if (!subdir.exists()) + subdir.mkdir(); + } + if (ignoreExistentFile && new File(strtemp).exists()) + continue; + try (FileOutputStream fos = new FileOutputStream(new File(strtemp))) { + IOUtils.copyTo(zipFile, fos, buf); + } + } + } + } + } + } + + /** + * Read the text content of a file in zip. + * + * @param file the zip file + * @param name the location of the text in zip file, something like A/B/C/D.txt + * @throws IOException if the file is not a valid zip file. + * @return the content of given file. + */ + public static String readTextZipEntry(File file, String name) throws IOException { + try (ZipFile zipFile = new ZipFile(file)) { + ZipArchiveEntry entry = zipFile.getEntry(name); + if (entry == null) + throw new IOException("ZipEntry `" + name + "` not found in " + file); + return IOUtils.readFullyAsString(zipFile.getInputStream(entry)); + } + } + + /** + * Read the text content of a file in zip. + * + * @param file the zip file + * @param location the location of the text in zip file, something like A/B/C/D.txt + * @return the content of given file. + */ + public static String readTextZipEntryQuietly(File file, String name) { + try { + return readTextZipEntry(file, name); + } catch (IOException e) { + return null; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.java new file mode 100644 index 000000000..935b1105d --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.java @@ -0,0 +1,88 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.awt.EventQueue; +import java.io.File; +import java.nio.charset.Charset; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.UUID; +import java.util.function.Consumer; +import org.jackhuang.hmcl.game.Argument; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.RuledArgument; +import org.jackhuang.hmcl.game.StringArgument; +import org.jackhuang.hmcl.task.Schedulers; + +/** + * Constants. + * + * @author huangyuhui + */ +public final class Constants { + + private Constants() { + } + + public static final String DEFAULT_ENCODING = "UTF-8"; + public static final Charset DEFAULT_CHARSET = Charsets.UTF_8; + public static final Charset SYSTEM_CHARSET = Charset.forName(OperatingSystem.ENCODING); + + public static final String DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/"; + public static final String DEFAULT_VERSION_DOWNLOAD_URL = "http://s3.amazonaws.com/Minecraft.Download/versions/"; + public static final String DEFAULT_INDEX_URL = "http://s3.amazonaws.com/Minecraft.Download/indexes/"; + + public static final SimpleDateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("HH:mm:ss"); + + public static Consumer UI_THREAD_SCHEDULER = s -> { + Schedulers.computation().schedule(s::run); + }; + + public static final Consumer SWING_UI_THREAD_SCHEDULER = s -> { + if (EventQueue.isDispatchThread()) + s.run(); + else + EventQueue.invokeLater(s); + }; + + public static final Consumer JAVAFX_UI_THREAD_SCHEDULER = s -> { + if (javafx.application.Platform.isFxApplicationThread()) + s.run(); + else + javafx.application.Platform.runLater(s); + }; + + public static final Gson GSON = new GsonBuilder() + .enableComplexMapKeySerialization() + .setPrettyPrinting() + .registerTypeAdapter(Library.class, Library.Serializer.INSTANCE) + .registerTypeAdapter(Argument.class, Argument.Serializer.INSTANCE) + .registerTypeAdapter(StringArgument.class, Argument.Serializer.INSTANCE) + .registerTypeAdapter(RuledArgument.class, RuledArgument.Serializer.INSTANCE) + .registerTypeAdapter(Date.class, DateTypeAdapter.INSTANCE) + .registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE) + .registerTypeAdapter(Platform.class, Platform.Serializer.INSTANCE) + .registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE) + .registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE) + .registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory.INSTANCE) + .create(); + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DateTypeAdapter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DateTypeAdapter.java new file mode 100644 index 000000000..bf2946555 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DateTypeAdapter.java @@ -0,0 +1,96 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.JsonSyntaxException; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * + * @author huangyuhui + */ +public final class DateTypeAdapter implements JsonSerializer, JsonDeserializer { + + public static final DateTypeAdapter INSTANCE = new DateTypeAdapter(); + + private DateTypeAdapter() { + } + + @Override + public JsonElement serialize(Date t, Type type, JsonSerializationContext jsc) { + synchronized (EN_US_FORMAT) { + return new JsonPrimitive(serializeToString(t)); + } + } + + @Override + public Date deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + if (!(json instanceof JsonPrimitive)) + throw new JsonParseException("The date should be a string value"); + else { + Date date = deserializeToDate(json.getAsString()); + if (type == Date.class) + return date; + else + throw new IllegalArgumentException(this.getClass().toString() + " cannot be deserialized to " + type); + } + } + + public static final DateFormat EN_US_FORMAT = DateFormat.getDateTimeInstance(2, 2, Locale.US); + public static final DateFormat ISO_8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + + public static Date deserializeToDate(String string) { + synchronized (EN_US_FORMAT) { + try { + return EN_US_FORMAT.parse(string); + } catch (ParseException ex1) { + try { + return ISO_8601_FORMAT.parse(string); + } catch (ParseException ex2) { + try { + String cleaned = string.replace("Z", "+00:00"); + cleaned = cleaned.substring(0, 22) + cleaned.substring(23); + return ISO_8601_FORMAT.parse(cleaned); + } catch (Exception e) { + throw new JsonSyntaxException("Invalid date: " + string, e); + } + } + } + } + } + + public static String serializeToString(Date date) { + synchronized (EN_US_FORMAT) { + String result = ISO_8601_FORMAT.format(date); + return result.substring(0, 22) + ":" + result.substring(22); + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DigestUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DigestUtils.java new file mode 100644 index 000000000..217df8920 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DigestUtils.java @@ -0,0 +1,251 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * + * @author huangyuhui + */ +public final class DigestUtils { + + private DigestUtils() { + } + + private static final int STREAM_BUFFER_LENGTH = 1024; + + private static byte[] digest(MessageDigest digest, InputStream data) + throws IOException { + return updateDigest(digest, data).digest(); + } + + public static MessageDigest getDigest(String algorithm) { + try { + return MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + } + + public static MessageDigest getMd2Digest() { + return getDigest("MD2"); + } + + public static MessageDigest getMd5Digest() { + return getDigest("MD5"); + } + + public static MessageDigest getSha1Digest() { + return getDigest("SHA-1"); + } + + public static MessageDigest getSha256Digest() { + return getDigest("SHA-256"); + } + + public static MessageDigest getSha384Digest() { + return getDigest("SHA-384"); + } + + public static MessageDigest getSha512Digest() { + return getDigest("SHA-512"); + } + + public static byte[] md2(byte[] data) { + return getMd2Digest().digest(data); + } + + public static byte[] md2(InputStream data) + throws IOException { + return digest(getMd2Digest(), data); + } + + public static byte[] md2(String data) { + return md2(data.getBytes(Charsets.UTF_8)); + } + + public static String md2Hex(byte[] data) { + return Hex.encodeHexString(md2(data)); + } + + public static String md2Hex(InputStream data) + throws IOException { + return Hex.encodeHexString(md2(data)); + } + + public static String md2Hex(String data) { + return Hex.encodeHexString(md2(data)); + } + + public static byte[] md5(byte[] data) { + return getMd5Digest().digest(data); + } + + public static byte[] md5(InputStream data) + throws IOException { + return digest(getMd5Digest(), data); + } + + public static byte[] md5(String data) { + return md5(data.getBytes(Charsets.UTF_8)); + } + + public static String md5Hex(byte[] data) { + return Hex.encodeHexString(md5(data)); + } + + public static String md5Hex(InputStream data) + throws IOException { + return Hex.encodeHexString(md5(data)); + } + + public static String md5Hex(String data) { + return Hex.encodeHexString(md5(data)); + } + + public static byte[] sha1(byte[] data) { + return getSha1Digest().digest(data); + } + + public static byte[] sha1(InputStream data) + throws IOException { + return digest(getSha1Digest(), data); + } + + public static byte[] sha1(String data) { + return sha1(data.getBytes(Charsets.UTF_8)); + } + + public static String sha1Hex(byte[] data) { + return Hex.encodeHexString(sha1(data)); + } + + public static String sha1Hex(InputStream data) + throws IOException { + return Hex.encodeHexString(sha1(data)); + } + + public static String sha1Hex(String data) { + return Hex.encodeHexString(sha1(data)); + } + + public static byte[] sha256(byte[] data) { + return getSha256Digest().digest(data); + } + + public static byte[] sha256(InputStream data) + throws IOException { + return digest(getSha256Digest(), data); + } + + public static byte[] sha256(String data) { + return sha256(data.getBytes(Charsets.UTF_8)); + } + + public static String sha256Hex(byte[] data) { + return Hex.encodeHexString(sha256(data)); + } + + public static String sha256Hex(InputStream data) + throws IOException { + return Hex.encodeHexString(sha256(data)); + } + + public static String sha256Hex(String data) { + return Hex.encodeHexString(sha256(data)); + } + + public static byte[] sha384(byte[] data) { + return getSha384Digest().digest(data); + } + + public static byte[] sha384(InputStream data) + throws IOException { + return digest(getSha384Digest(), data); + } + + public static byte[] sha384(String data) { + return sha384(data.getBytes(Charsets.UTF_8)); + } + + public static String sha384Hex(byte[] data) { + return Hex.encodeHexString(sha384(data)); + } + + public static String sha384Hex(InputStream data) + throws IOException { + return Hex.encodeHexString(sha384(data)); + } + + public static String sha384Hex(String data) { + return Hex.encodeHexString(sha384(data)); + } + + public static byte[] sha512(byte[] data) { + return getSha512Digest().digest(data); + } + + public static byte[] sha512(InputStream data) + throws IOException { + return digest(getSha512Digest(), data); + } + + public static byte[] sha512(String data) { + return sha512(data.getBytes(Charsets.UTF_8)); + } + + public static String sha512Hex(byte[] data) { + return Hex.encodeHexString(sha512(data)); + } + + public static String sha512Hex(InputStream data) + throws IOException { + return Hex.encodeHexString(sha512(data)); + } + + public static String sha512Hex(String data) { + return Hex.encodeHexString(sha512(data)); + } + + public static MessageDigest updateDigest(MessageDigest messageDigest, byte[] valueToDigest) { + messageDigest.update(valueToDigest); + return messageDigest; + } + + public static MessageDigest updateDigest(MessageDigest digest, InputStream data) + throws IOException { + byte[] buffer = new byte[STREAM_BUFFER_LENGTH]; + int read = data.read(buffer, 0, STREAM_BUFFER_LENGTH); + + while (read > -1) { + digest.update(buffer, 0, read); + read = data.read(buffer, 0, STREAM_BUFFER_LENGTH); + } + + return digest; + } + + public static MessageDigest updateDigest(MessageDigest messageDigest, String valueToDigest) { + messageDigest.update(valueToDigest.getBytes(Charsets.UTF_8)); + return messageDigest; + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/ResultEvent.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExceptionalFunction.java similarity index 82% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/ResultEvent.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExceptionalFunction.java index 7ecbf4a88..008b84d57 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/ResultEvent.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExceptionalFunction.java @@ -15,6 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.event +package org.jackhuang.hmcl.util; -open class FailedEvent(source: Any, val failedTime: Int, var newResult: T) : Event(source) \ No newline at end of file +/** + * + * @author huangyuhui + */ +public interface ExceptionalFunction { + R apply(T t) throws E; +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetObject.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExceptionalRunnable.java similarity index 65% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetObject.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExceptionalRunnable.java index 8125ce4dd..0e937a218 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetObject.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExceptionalRunnable.java @@ -15,18 +15,27 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.game +package org.jackhuang.hmcl.util; -import org.jackhuang.hmcl.util.Validation +import java.util.concurrent.Callable; -data class AssetObject @JvmOverloads constructor( - val hash: String = "", - val size: Long = 0 -): Validation { - val location: String - get() = hash.substring(0, 2) + "/" + hash +/** + * + * @author huangyuhui + */ +public interface ExceptionalRunnable { - override fun validate() { - check(hash.isNotBlank(), { "AssetObject hash cannot be blank." }) + void run() throws E; + + default Callable toCallable() { + return () -> { + run(); + return null; + }; } -} \ No newline at end of file + + public static ExceptionalRunnable fromRunnable(Runnable r) { + return r::run; + } + +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SilentException.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExceptionalSupplier.java similarity index 74% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SilentException.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExceptionalSupplier.java index 331457f9e..b679bfa5d 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SilentException.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ExceptionalSupplier.java @@ -15,9 +15,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.task +package org.jackhuang.hmcl.util; + +import java.util.concurrent.Callable; /** - * If a task throws [SilentException], the task will be marked as failure but do not log the stacktrace. + * + * @author huangyuhui */ -class SilentException : Exception() \ No newline at end of file +public interface ExceptionalSupplier { + R get() throws E; + + default Callable toCallable() { + return this::get; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileTypeAdapter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileTypeAdapter.java new file mode 100644 index 000000000..8482c49ee --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileTypeAdapter.java @@ -0,0 +1,58 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.io.File; +import java.lang.reflect.Type; + +/** + * + * @author huangyuhui + */ +public final class FileTypeAdapter implements JsonSerializer, JsonDeserializer { + + public static final FileTypeAdapter INSTANCE = new FileTypeAdapter(); + + private FileTypeAdapter() { + } + + @Override + public JsonElement serialize(File t, Type type, JsonSerializationContext jsc) { + if (t == null) + return JsonNull.INSTANCE; + else + return new JsonPrimitive(t.getPath()); + } + + @Override + public File deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { + if (je == null) + return null; + else + return new File(je.getAsString()); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.java new file mode 100644 index 000000000..ad464573e --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.java @@ -0,0 +1,286 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +/** + * + * @author huang + */ +public final class FileUtils { + + private FileUtils() { + } + + public static String getNameWithoutExtension(File file) { + return StringUtils.substringBeforeLast(file.getName(), '.'); + } + + public static String getExtension(File file) { + return StringUtils.substringAfterLast(file.getName(), '.'); + } + + public static String readText(File file) throws IOException { + return readText(file, Charsets.DEFAULT_CHARSET); + } + + public static String readText(File file, Charset charset) throws IOException { + return new String(readBytes(file), charset); + } + + public static byte[] readBytes(File file) throws IOException { + try (FileInputStream input = new FileInputStream(file)) { + if (file.length() > Integer.MAX_VALUE) + throw new OutOfMemoryError("File " + file + " is too big (" + file.length() + " bytes) to fit in memory."); + return IOUtils.readFullyAsByteArray(input); + } + } + + public static void writeText(File file, String text) throws IOException { + writeText(file, text, Charsets.DEFAULT_CHARSET); + } + + public static void writeText(File file, String text, Charset charset) throws IOException { + writeBytes(file, text.getBytes(charset)); + } + + public static void writeBytes(File file, byte[] array) throws IOException { + try (FileOutputStream stream = new FileOutputStream(file)) { + stream.write(array); + } + } + + public static void deleteDirectory(File directory) + throws IOException { + if (!directory.exists()) + return; + + if (!isSymlink(directory)) + cleanDirectory(directory); + + if (!directory.delete()) { + String message = "Unable to delete directory " + directory + "."; + + throw new IOException(message); + } + } + + public static boolean deleteDirectoryQuietly(File directory) { + return Lang.test(() -> deleteDirectory(directory)); + } + + public static boolean cleanDirectoryQuietly(File directory) { + return Lang.test(() -> cleanDirectory(directory)); + } + + public static void cleanDirectory(File directory) + throws IOException { + if (!directory.exists()) { + if (!makeDirectory(directory)) + throw new IOException("Failed to create directory: " + directory); + return; + } + + if (!directory.isDirectory()) { + String message = directory + " is not a directory"; + throw new IllegalArgumentException(message); + } + + File[] files = directory.listFiles(); + if (files == null) + throw new IOException("Failed to list contents of " + directory); + + IOException exception = null; + for (File file : files) + try { + forceDelete(file); + } catch (IOException ioe) { + exception = ioe; + } + + if (null != exception) + throw exception; + } + + public static void forceDelete(File file) + throws IOException { + if (file.isDirectory()) + deleteDirectory(file); + else { + boolean filePresent = file.exists(); + if (!file.delete()) { + if (!filePresent) + throw new FileNotFoundException("File does not exist: " + file); + String message = "Unable to delete file: " + file; + + throw new IOException(message); + } + } + } + + public static boolean isSymlink(File file) + throws IOException { + Objects.requireNonNull(file, "File must not be null"); + if (File.separatorChar == '\\') + return false; + File fileInCanonicalDir; + if (file.getParent() == null) + fileInCanonicalDir = file; + else { + File canonicalDir = file.getParentFile().getCanonicalFile(); + fileInCanonicalDir = new File(canonicalDir, file.getName()); + } + + return !fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile()); + } + + public static void copyDirectory(File srcDir, File destDir) + throws IOException { + copyDirectory(srcDir, destDir, null); + } + + public static void copyDirectory(File srcDir, File destDir, FileFilter filter) + throws IOException { + Objects.requireNonNull(srcDir, "Source must not be null"); + Objects.requireNonNull(destDir, "Destination must not be null"); + if (!srcDir.exists()) + throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); + if (!srcDir.isDirectory()) + throw new IOException("Source '" + srcDir + "' exists but is not a directory"); + if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) + throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same"); + + List exclusionList = null; + if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { + File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); + if ((srcFiles != null) && (srcFiles.length > 0)) { + exclusionList = new ArrayList<>(srcFiles.length); + for (File srcFile : srcFiles) { + File copiedFile = new File(destDir, srcFile.getName()); + exclusionList.add(copiedFile.getCanonicalPath()); + } + } + } + doCopyDirectory(srcDir, destDir, filter, exclusionList); + } + + private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter, List exclusionList) + throws IOException { + File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); + if (srcFiles == null) + throw new IOException("Failed to list contents of " + srcDir); + if (destDir.exists()) { + if (!destDir.isDirectory()) + throw new IOException("Destination '" + destDir + "' exists but is not a directory"); + } else if (!FileUtils.makeDirectory(destDir)) + throw new IOException("Destination '" + destDir + "' directory cannot be created"); + + if (!destDir.canWrite()) + throw new IOException("Destination '" + destDir + "' cannot be written to"); + for (File srcFile : srcFiles) { + File dstFile = new File(destDir, srcFile.getName()); + if ((exclusionList == null) || (!exclusionList.contains(srcFile.getCanonicalPath()))) + if (srcFile.isDirectory()) + doCopyDirectory(srcFile, dstFile, filter, exclusionList); + else + doCopyFile(srcFile, dstFile); + } + destDir.setLastModified(srcDir.lastModified()); + } + + public static void copyFileQuietly(File srcFile, File destFile) { + try { + copyFile(srcFile, destFile); + } catch (IOException ex) { + } + } + + public static void copyFile(File srcFile, File destFile) + throws IOException { + Objects.requireNonNull(srcFile, "Source must not be null"); + Objects.requireNonNull(destFile, "Destination must not be null"); + if (!srcFile.exists()) + throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); + if (srcFile.isDirectory()) + throw new IOException("Source '" + srcFile + "' exists but is a directory"); + if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) + throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); + File parentFile = destFile.getParentFile(); + if (parentFile != null && !FileUtils.makeDirectory(parentFile)) + throw new IOException("Destination '" + parentFile + "' directory cannot be created"); + if (destFile.exists() && !destFile.canWrite()) + throw new IOException("Destination '" + destFile + "' exists but is read-only"); + doCopyFile(srcFile, destFile); + } + + public static void doCopyFile(File srcFile, File destFile) + throws IOException { + Files.copy(srcFile.toPath(), destFile.toPath(), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); + } + + public static boolean makeDirectory(File directory) { + return directory.isDirectory() || directory.mkdirs(); + } + + public static boolean makeFile(File file) { + if (!makeDirectory(file.getAbsoluteFile().getParentFile())) + return false; + if (!file.exists() && !Lang.test(file::createNewFile)) + return false; + return true; + } + + public static List listFilesByExtension(File file, String extension) { + List result = new LinkedList<>(); + File[] files = file.listFiles(); + if (files != null) + for (File it : files) + if (extension.equals(getExtension(it))) + result.add(it); + return result; + } + + public static File createTempFile() throws IOException { + return createTempFile("tmp"); + } + + public static File createTempFile(String prefix) throws IOException { + return createTempFile(prefix, null); + } + + public static File createTempFile(String prefix, String suffix) throws IOException { + return createTempFile(prefix, suffix, null); + } + + public static File createTempFile(String prefix, String suffix, File directory) throws IOException { + return File.createTempFile(prefix, suffix, directory); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Hex.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Hex.java new file mode 100644 index 000000000..6feeec8f2 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Hex.java @@ -0,0 +1,128 @@ +/* + * Hello Minecraft!. + * Copyright (C) 2013 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.nio.charset.Charset; + +public final class Hex { + + private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + private final Charset charset; + + public static byte[] decodeHex(char[] data) throws Exception { + int len = data.length; + + if ((len & 0x1) != 0) + throw new Exception("Odd number of characters."); + + byte[] out = new byte[len >> 1]; + + int i = 0; + for (int j = 0; j < len; i++) { + int f = toDigit(data[j], j) << 4; + j++; + f |= toDigit(data[j], j); + j++; + out[i] = (byte) (f & 0xFF); + } + + return out; + } + + public static char[] encodeHex(byte[] data) { + return encodeHex(data, true); + } + + public static char[] encodeHex(byte[] data, boolean toLowerCase) { + return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + protected static char[] encodeHex(byte[] data, char[] toDigits) { + int l = data.length; + char[] out = new char[l << 1]; + + int i = 0; + for (int j = 0; i < l; i++) { + out[(j++)] = toDigits[((0xF0 & data[i]) >>> 4)]; + out[(j++)] = toDigits[(0xF & data[i])]; + } + return out; + } + + public static String encodeHexString(byte[] data) { + return new String(encodeHex(data)); + } + + protected static int toDigit(char ch, int index) { + int digit = Character.digit(ch, 16); + if (digit == -1) + throw new IllegalArgumentException("Illegal hexadecimal character " + ch + " at index " + index); + return digit; + } + + public Hex() { + this(Charsets.DEFAULT_CHARSET); + } + + public Hex(Charset charset) { + this.charset = charset; + } + + public byte[] decode(byte[] array) throws Exception { + return decodeHex(new String(array, getCharset()).toCharArray()); + } + + public Object decode(Object object) throws Exception { + try { + char[] charArray = (object instanceof String) ? ((String) object).toCharArray() : (char[]) (char[]) object; + return decodeHex(charArray); + } catch (ClassCastException e) { + throw new Exception(e.getMessage(), e); + } + } + + public byte[] encode(byte[] array) { + return encodeHexString(array).getBytes(getCharset()); + } + + public Object encode(Object object) + throws Exception { + try { + byte[] byteArray = (object instanceof String) ? ((String) object).getBytes(getCharset()) : (byte[]) (byte[]) object; + + return encodeHex(byteArray); + } catch (ClassCastException e) { + throw new Exception(e.getMessage(), e); + } + } + + public Charset getCharset() { + return this.charset; + } + + public String getCharsetName() { + return this.charset.name(); + } + + @Override + public String toString() { + return super.toString() + "[charsetName=" + this.charset + "]"; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/IOUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/IOUtils.java new file mode 100644 index 000000000..26e5f20f5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/IOUtils.java @@ -0,0 +1,80 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * + * @author huang + */ +public final class IOUtils { + + private IOUtils() { + } + + public static final int DEFAULT_BUFFER_SIZE = 8 * 1024; + + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) + closeable.close(); + } catch (IOException e) { + } + } + + public static ByteArrayOutputStream readFully(InputStream stream) throws IOException { + try { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + copyTo(stream, result); + return result; + } finally { + closeQuietly(stream); + } + } + + public static byte[] readFullyAsByteArray(InputStream stream) throws IOException { + return readFully(stream).toByteArray(); + } + + public static String readFullyAsString(InputStream stream) throws IOException { + return readFully(stream).toString(); + } + + public static String readFullyAsString(InputStream stream, Charset charset) throws IOException { + return Lang.invoke(() -> readFully(stream).toString(charset.name())); + } + + public static void copyTo(InputStream src, OutputStream dest) throws IOException { + copyTo(src, dest, new byte[DEFAULT_BUFFER_SIZE]); + } + + public static void copyTo(InputStream src, OutputStream dest, byte[] buf) throws IOException { + while (true) { + int len = src.read(buf); + if (len == -1) + break; + dest.write(buf, 0, len); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateBooleanProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateBooleanProperty.java new file mode 100644 index 000000000..42929c8d2 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateBooleanProperty.java @@ -0,0 +1,61 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.Objects; +import java.util.function.Consumer; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +/** + * + * @author huangyuhui + */ +public class ImmediateBooleanProperty extends SimpleBooleanProperty { + + @Override + public void set(boolean newValue) { + super.get(); + super.set(newValue); + } + + @Override + public void bind(ObservableValue newObservable) { + super.get(); + super.bind(newObservable); + } + + @Override + public void unbind() { + super.get(); + super.unbind(); + } + + private Consumer listener = Lang.EMPTY_CONSUMER; + private final ChangeListener changeListener = (a, b, newValue) -> listener.accept(newValue); + + public void setChangedListener(Consumer listener) { + this.listener = Objects.requireNonNull(listener); + } + + public ImmediateBooleanProperty(Object bean, String name, boolean initialValue) { + super(bean, name, initialValue); + addListener(changeListener); + } +} \ No newline at end of file diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateDoubleProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateDoubleProperty.java new file mode 100644 index 000000000..76599a7d2 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateDoubleProperty.java @@ -0,0 +1,63 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + + +import java.util.Objects; +import java.util.function.Consumer; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +/** + * + * @author huangyuhui + */ +public class ImmediateDoubleProperty extends SimpleDoubleProperty { + + @Override + public void set(double newValue) { + super.get(); + super.set(newValue); + } + + @Override + public void bind(ObservableValue newObservable) { + super.get(); + super.bind(newObservable); + } + + @Override + public void unbind() { + super.get(); + super.unbind(); + } + + private Consumer listener = Lang.EMPTY_CONSUMER; + private final ChangeListener changeListener = (a, b, newValue) -> listener.accept(newValue.doubleValue()); + + public void setChangedListener(Consumer listener) { + this.listener = Objects.requireNonNull(listener); + } + + public ImmediateDoubleProperty(Object bean, String name, double initialValue) { + super(bean, name, initialValue); + addListener(changeListener); + } +} + diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateIntegerProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateIntegerProperty.java new file mode 100644 index 000000000..bfe707364 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateIntegerProperty.java @@ -0,0 +1,61 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.Objects; +import java.util.function.Consumer; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +/** + * + * @author huangyuhui + */ +public class ImmediateIntegerProperty extends SimpleIntegerProperty { + + @Override + public void set(int newValue) { + super.get(); + super.set(newValue); + } + + @Override + public void bind(ObservableValue newObservable) { + super.get(); + super.bind(newObservable); + } + + @Override + public void unbind() { + super.get(); + super.unbind(); + } + + private Consumer listener = Lang.EMPTY_CONSUMER; + private final ChangeListener changeListener = (a, b, newValue) -> listener.accept(newValue.intValue()); + + public void setChangedListener(Consumer listener) { + this.listener = Objects.requireNonNull(listener); + } + + public ImmediateIntegerProperty(Object bean, String name, int initialValue) { + super(bean, name, initialValue); + addListener(changeListener); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateObjectProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateObjectProperty.java new file mode 100644 index 000000000..a71365f1b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateObjectProperty.java @@ -0,0 +1,61 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.Objects; +import java.util.function.Consumer; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +/** + * + * @author huangyuhui + */ +public class ImmediateObjectProperty extends SimpleObjectProperty { + + @Override + public void set(T newValue) { + super.get(); + super.set(newValue); + } + + @Override + public void bind(ObservableValue newObservable) { + super.get(); + super.bind(newObservable); + } + + @Override + public void unbind() { + super.get(); + super.unbind(); + } + + private Consumer listener = Lang.EMPTY_CONSUMER; + private final ChangeListener changeListener = (a, b, newValue) -> listener.accept(newValue); + + public void setChangedListener(Consumer listener) { + this.listener = Objects.requireNonNull(listener); + } + + public ImmediateObjectProperty(Object bean, String name, T initialValue) { + super(bean, name, initialValue); + addListener(changeListener); + } +} \ No newline at end of file diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateStringProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateStringProperty.java new file mode 100644 index 000000000..0b17dc039 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ImmediateStringProperty.java @@ -0,0 +1,61 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.Objects; +import java.util.function.Consumer; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +/** + * + * @author huangyuhui + */ +public class ImmediateStringProperty extends SimpleStringProperty { + + @Override + public void set(String newValue) { + super.get(); + super.set(newValue); + } + + @Override + public void bind(ObservableValue newObservable) { + super.get(); + super.bind(newObservable); + } + + @Override + public void unbind() { + super.get(); + super.unbind(); + } + + private Consumer listener = Lang.EMPTY_CONSUMER; + private final ChangeListener changeListener = (a, b, newValue) -> listener.accept(newValue); + + public void setChangedListener(Consumer listener) { + this.listener = Objects.requireNonNull(listener); + } + + public ImmediateStringProperty(Object bean, String name, String initialValue) { + super(bean, name, initialValue); + addListener(changeListener); + } +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskResult.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Immutable.java similarity index 75% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskResult.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/util/Immutable.java index 2078f2563..094f2032e 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskResult.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Immutable.java @@ -15,16 +15,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.task +package org.jackhuang.hmcl.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; /** - * A task that has a result. + * Mark if instances of the class are immutable. + * + * @author huangyuhui */ -abstract class TaskResult : Task() { - open var result: V? = null - - /** - * The task id, will be stored as key of [variables] - */ - abstract val id: String -} \ No newline at end of file +@Target(ElementType.TYPE) +public @interface Immutable { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/IntVersionNumber.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/IntVersionNumber.java new file mode 100644 index 000000000..7fbb24966 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/IntVersionNumber.java @@ -0,0 +1,75 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.List; + +/** + * If a version string formats x.x.x.x, a {@code IntVersionNumber} + * will be generated. + * + * @author huangyuhui + */ +public final class IntVersionNumber extends VersionNumber { + + private final List version; + + IntVersionNumber(List version) { + this.version = version; + } + + public int get(int index) { + return version.get(index); + } + + @Override + public int compareTo(VersionNumber o) { + if (!(o instanceof IntVersionNumber)) + return 0; + IntVersionNumber other = (IntVersionNumber) o; + int len = Math.min(this.version.size(), other.version.size()); + for (int i = 0; i < len; ++i) + if (!version.get(i).equals(other.version.get(i))) + return version.get(i).compareTo(other.version.get(i)); + return Integer.compare(this.version.size(), other.version.size()); + } + + @Override + public int hashCode() { + int hash = 3; + for (int i : version) + hash = 83 * hash + i; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof VersionNumber)) + return false; + if (!(obj instanceof IntVersionNumber)) + return true; + IntVersionNumber other = (IntVersionNumber) obj; + if (version.size() != other.version.size()) + return false; + + for (int i = 0; i < version.size(); ++i) + if (!version.get(i).equals(other.version.get(i))) + return false; + return true; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/JavaVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/JavaVersion.java new file mode 100644 index 000000000..2f4f30d9a --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/JavaVersion.java @@ -0,0 +1,270 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents a Java installation. + * + * @author huangyuhui + */ +public final class JavaVersion implements Serializable { + + private final File binary; + private final String longVersion; + private final Platform platform; + private final int version; + + public JavaVersion(File binary, String longVersion, Platform platform) { + this.binary = binary; + this.longVersion = longVersion; + this.platform = platform; + version = parseVersion(longVersion); + } + + public File getBinary() { + return binary; + } + + public String getVersion() { + return longVersion; + } + + public Platform getPlatform() { + return platform; + } + + /** + * The major version of Java installation. + * + * @see org.jackhuang.hmcl.util.JavaVersion#JAVA_X + * @see org.jackhuang.hmcl.util.JavaVersion#JAVA_9 + * @see org.jackhuang.hmcl.util.JavaVersion#JAVA_8 + * @see org.jackhuang.hmcl.util.JavaVersion#JAVA_7 + * @see org.jackhuang.hmcl.util.JavaVersion#JAVA_6 + * @see org.jackhuang.hmcl.util.JavaVersion#JAVA_5 + * @see org.jackhuang.hmcl.util.JavaVersion#UNKNOWN + */ + public int getParsedVersion() { + return version; + } + + private static final Pattern REGEX = Pattern.compile("java version \"(?(.*?))\""); + + public static final int UNKNOWN = -1; + public static final int JAVA_5 = 50; + public static final int JAVA_6 = 60; + public static final int JAVA_7 = 70; + public static final int JAVA_8 = 80; + public static final int JAVA_9 = 90; + public static final int JAVA_X = 100; + + private static int parseVersion(String version) { + if (version.startsWith("10") || version.startsWith("X")) + return JAVA_X; + else if (version.contains("1.9.") || version.startsWith("9")) + return JAVA_9; + else if (version.contains("1.8")) + return JAVA_8; + else if (version.contains("1.7")) + return JAVA_7; + else if (version.contains("1.6")) + return JAVA_6; + else if (version.contains("1.5")) + return JAVA_5; + else + return UNKNOWN; + } + + public static JavaVersion fromExecutable(File executable) throws IOException { + File actualFile = executable; + Platform platform = Platform.BIT_32; + String version = null; + + if ("javaw".equals(FileUtils.getNameWithoutExtension(actualFile))) + actualFile = new File(actualFile.getAbsoluteFile().getParentFile(), "java"); + + try { + Process process = new ProcessBuilder(actualFile.getAbsolutePath(), "-version").start(); + process.waitFor(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); + String line; + while ((line = reader.readLine()) != null) { + Matcher m = REGEX.matcher(line); + if (m.find()) + version = m.group("version"); + if (line.contains("64-Bit")) + platform = Platform.BIT_64; + } + } catch (InterruptedException e) { + throw new IOException("Interrupted scanning the java version.", e); + } + + if (version == null) + throw new IOException("No matched Java version."); + + if (parseVersion(version) == UNKNOWN) + throw new IOException("Unrecognized Java version " + version); + return new JavaVersion(actualFile, version, platform); + } + + private static JavaVersion fromKnownExecutable(File file, String version) { + return new JavaVersion(file, version, Platform.UNKNOWN); + } + + public static JavaVersion fromJavaHome(File home) throws IOException { + return fromExecutable(getJavaFile(home)); + } + + private static File getJavaFile(File home) { + File path = new File(home, "bin"); + File javaw = new File(path, "javae.exe"); + if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && javaw.isFile()) + return javaw; + else + return new File(path, "java"); // Both linux and windows allow this. + } + + public static JavaVersion fromCurrentEnvironment() { + return THIS_JAVA; + } + + public static final JavaVersion THIS_JAVA = new JavaVersion( + getJavaFile(new File(System.getProperty("java.home"))), + System.getProperty("java.version"), + Platform.PLATFORM + ); + + private static Map JAVAS; + private static final CountDownLatch LATCH = new CountDownLatch(1); + + public static Map getJREs() throws InterruptedException { + if (JAVAS != null) + return JAVAS; + LATCH.await(); + return JAVAS; + } + + public static synchronized void initialize() throws IOException, InterruptedException { + if (JAVAS != null) + throw new IllegalStateException("JavaVersions have already been initialized."); + HashMap temp = new HashMap<>(); + temp.put(THIS_JAVA.getVersion(), THIS_JAVA); + List javaVersions; + switch (OperatingSystem.CURRENT_OS) { + case WINDOWS: + javaVersions = queryWindows(); + break; + case OSX: + javaVersions = queryMacintosh(); + break; + default: + javaVersions = Collections.EMPTY_LIST; + break; + } + for (JavaVersion v : javaVersions) + temp.put(v.getVersion(), v); + JAVAS = Collections.unmodifiableMap(temp); + LATCH.countDown(); + } + + private static List queryMacintosh() throws IOException { + LinkedList res = new LinkedList<>(); + + File currentJRE = new File("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home"); + if (currentJRE.exists()) + res.add(fromJavaHome(currentJRE)); + File[] files = new File("/Library/Java/JavaVirtualMachines/").listFiles(); + if (files != null) + for (File file : files) + res.add(fromJavaHome(new File(file, "Contents/Home"))); + + return res; + } + + private static List queryWindows() throws IOException, InterruptedException { + LinkedList res = new LinkedList<>(); + Lang.ignoringException(() -> res.addAll(queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\"))); + Lang.ignoringException(() -> res.addAll(queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\"))); + Lang.ignoringException(() -> res.addAll(queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\"))); + Lang.ignoringException(() -> res.addAll(queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\"))); + return res; + } + + private static List querySubFolders(String location) throws IOException, InterruptedException { + List res = new LinkedList<>(); + String[] cmd = new String[] { "cmd", "/c", "reg", "query", location }; + Process process = Runtime.getRuntime().exec(cmd); + process.waitFor(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) + if (line.startsWith(location) && !line.equals(location)) + res.add(line); + return res; + } + + private static List queryRegisterKey(String location) throws IOException, InterruptedException { + List res = new LinkedList<>(); + for (String java : querySubFolders(location)) { + String home = queryRegisterValue(java, "JavaHome"); + if (home != null) + res.add(fromJavaHome(new File(home))); + } + return res; + } + + private static String queryRegisterValue(String location, String name) throws IOException, InterruptedException { + String[] cmd = new String[] { "cmd", "/c", "reg", "query", location, "/v", name }; + boolean last = false; + Process process = Runtime.getRuntime().exec(cmd); + process.waitFor(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) + if (StringUtils.isNotBlank(line)) { + if (last && line.trim().startsWith(name)) { + int begins = line.indexOf(name); + if (begins > 0) { + String s2 = line.substring(begins + name.length()); + begins = s2.indexOf("REG_SZ"); + if (begins > 0) + return s2.substring(begins + "REG_SZ".length()).trim(); + } + } + if (location.equals(line.trim())) + last = true; + } + return null; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java new file mode 100644 index 000000000..81c1c35ef --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java @@ -0,0 +1,247 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.jackhuang.hmcl.util; + +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * + * @author huangyuhui + */ +public final class Lang { + + private Lang() { + } + + public static final Consumer EMPTY_CONSUMER = a -> { + }; + + public static Map mapOf(Pair... pairs) { + HashMap map = new HashMap<>(); + for (Pair pair : pairs) + map.put(pair.getKey(), pair.getValue()); + return map; + } + + public static V getOrPut(Map map, K key, Supplier defaultValue) { + V value = map.get(key); + if (value == null) { + V answer = defaultValue.get(); + map.put(key, answer); + return answer; + } else + return value; + } + + public static void throwable(Throwable exception) throws E { + throw (E) exception; + } + + /** + * This method will call a method without checked exceptions + * by treating the compiler. + * + * If this method throws a checked exception, + * it will still abort the application because of the exception. + * + * @param type of argument. + * @param type of result. + * @param supplier your method. + * @return the result of the method to invoke. + */ + public static R invoke(ExceptionalFunction function, T t) { + try { + return function.apply(t); + } catch (Exception e) { + throwable(e); + return null; // won't get to here. + } + } + + public static Function hideException(ExceptionalFunction function) { + return r -> invoke(function, r); + } + + public static Function liftException(ExceptionalFunction function) throws E { + return hideException(function); + } + + /** + * This method will call a method without checked exceptions + * by treating the compiler. + * + * If this method throws a checked exception, + * it will still abort the application because of the exception. + * + * @param type of result. + * @param supplier your method. + * @return the result of the method to invoke. + */ + public static T invoke(ExceptionalSupplier supplier) { + try { + return supplier.get(); + } catch (Exception e) { + throwable(e); + return null; // won't get to here. + } + } + + public static Supplier hideException(ExceptionalSupplier supplier) { + return () -> invoke(supplier); + } + + public static Supplier liftException(ExceptionalSupplier supplier) throws E { + return hideException(supplier); + } + + public static boolean test(ExceptionalSupplier r) { + try { + return r.get(); + } catch (Exception e) { + return false; + } + } + + /** + * This method will call a method without checked exceptions + * by treating the compiler. + * + * If this method throws a checked exception, + * it will still abort the application because of the exception. + * + * @param r your method. + */ + public static void invoke(ExceptionalRunnable r) { + try { + r.run(); + } catch (Exception e) { + throwable(e); + } + } + + public static Runnable hideException(ExceptionalRunnable r) { + return () -> invoke(r); + } + + public static Runnable liftException(ExceptionalRunnable r) throws E { + return hideException(r); + } + + public static boolean test(ExceptionalRunnable r) { + try { + r.run(); + return true; + } catch (Exception e) { + return false; + } + } + + public static T ignoringException(ExceptionalSupplier supplier) { + return ignoringException(supplier, null); + } + + public static T ignoringException(ExceptionalSupplier supplier, T defaultValue) { + try { + return supplier.get(); + } catch (Exception e) { + return defaultValue; + } + } + + public static void ignoringException(ExceptionalRunnable runnable) { + try { + runnable.run(); + } catch (Exception e) { + } + } + + public static Optional convert(Object o, Class clazz) { + if (o == null || !ReflectionHelper.isInstance(clazz, o)) + return Optional.empty(); + else + return Optional.of((V) o); + } + + public static V convert(Object o, Class clazz, V defaultValue) { + if (o == null || !ReflectionHelper.isInstance(clazz, o)) + return defaultValue; + else + return (V) o; + } + + public static Optional get(Map map, Object key, Class clazz) { + return convert(map.get(key), clazz); + } + + public static V get(Map map, Object key, Class clazz, V defaultValue) { + return convert(map.get(key), clazz, defaultValue); + } + + public static List merge(Collection... collections) { + LinkedList result = new LinkedList<>(); + for (Collection collection : collections) + if (collection != null) + result.addAll(collection); + return result; + } + + public static T nonNull(T... t) { + for (T a : t) + if (a != null) + return a; + return null; + } + + public static Thread thread(Runnable runnable) { + return thread(runnable, null); + } + + public static Thread thread(Runnable runnable, String name) { + return thread(runnable, name, false); + } + + public static Thread thread(Runnable runnable, String name, boolean isDaemon) { + Thread thread = new Thread(runnable); + if (isDaemon) + thread.setDaemon(true); + if (name != null) + thread.setName(name); + thread.start(); + return thread; + } + + public static T get(Optional optional) { + return optional.isPresent() ? optional.get() : null; + } + + public static Iterator asIterator(Enumeration enumeration) { + return new Iterator() { + @Override + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + + @Override + public T next() { + return enumeration.nextElement(); + } + }; + } + + public static Iterable asIterable(Enumeration enumeration) { + return () -> asIterator(enumeration); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java new file mode 100644 index 000000000..c113874e4 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java @@ -0,0 +1,141 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javafx.scene.paint.Color; + +/** + * + * @author huangyuhui + */ +public enum Log4jLevel { + FATAL(1, Color.web("#F7A699")), + ERROR(2, Color.web("#FFCCBB")), + WARN(3, Color.web("#FFEECC")), + INFO(4, Color.web("#FBFBFB")), + DEBUG(5, Color.web("#EEE9E0")), + TRACE(6, Color.BLUE), + ALL(2147483647, Color.BLACK); + + private final int level; + private final Color color; + + private Log4jLevel(int level, Color color) { + this.level = level; + this.color = color; + } + + public int getLevel() { + return level; + } + + public Color getColor() { + return color; + } + + public boolean lessOrEqual(Log4jLevel level) { + return this.level <= level.level; + } + public static final Pattern MINECRAFT_LOGGER = Pattern.compile("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); + public static final Pattern MINECRAFT_LOGGER_CATEGORY = Pattern.compile("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\] \\[(?[^\\]]+)\\]"); + public static final String JAVA_SYMBOL = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; + + public static Log4jLevel guessLevel(String line) { + Log4jLevel level = null; + Matcher m = MINECRAFT_LOGGER.matcher(line); + if (m.find()) { + // New style logs from log4j + String levelStr = m.group("level"); + if (null != levelStr) + switch (levelStr) { + case "INFO": + level = INFO; + break; + case "WARN": + level = WARN; + break; + case "ERROR": + level = ERROR; + break; + case "FATAL": + level = FATAL; + break; + case "TRACE": + level = TRACE; + break; + case "DEBUG": + level = DEBUG; + break; + default: + break; + } + Matcher m2 = MINECRAFT_LOGGER_CATEGORY.matcher(line); + if (m2.find()) { + String level2Str = m2.group("category"); + if (null != level2Str) + switch (level2Str) { + case "STDOUT": + level = INFO; + break; + case "STDERR": + level = ERROR; + break; + } + } + } else { + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") + || line.contains("[FINER]") || line.contains("[FINEST]")) + level = INFO; + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + level = ERROR; + if (line.contains("[WARNING]")) + level = WARN; + if (line.contains("[DEBUG]")) + level = DEBUG; + } + if (line.contains("overwriting existing")) + level = FATAL; + + /*if (line.contains("Exception in thread") + || line.matches("\\s+at " + JAVA_SYMBOL) + || line.matches("Caused by: " + JAVA_SYMBOL) + || line.matches("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)") + || line.matches("... \\d+ more$")) + return ERROR;*/ + return level; + } + + public static boolean isError(Log4jLevel a) { + return a == null ? false : a.lessOrEqual(Log4jLevel.ERROR); + } + + public static Log4jLevel mergeLevel(Log4jLevel a, Log4jLevel b) { + if (a == null) + return b; + else if (b == null) + return a; + else + return a.level < b.level ? a : b; + } + + public static boolean guessLogLineError(String log) { + return isError(guessLevel(log)); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Logging.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Logging.java new file mode 100644 index 000000000..100f955d0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Logging.java @@ -0,0 +1,80 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +/** + * + * @author huangyuhui + */ +public final class Logging { + + public static final Logger LOG; + + static { + LOG = Logger.getLogger("HMCL"); + LOG.setLevel(Level.FINER); + LOG.setUseParentHandlers(false); + + try { + FileHandler fileHandler = new FileHandler("hmcl.log"); + fileHandler.setLevel(Level.FINEST); + LOG.addHandler(fileHandler); + } catch (IOException e) { + System.err.println("Unable to create hmcl.log, " + e.getMessage()); + } + + ConsoleHandler consoleHandler = new ConsoleHandler(); + consoleHandler.setLevel(Level.FINER); + consoleHandler.setFormatter(DefaultFormatter.INSTANCE); + LOG.addHandler(consoleHandler); + } + + static final class DefaultFormatter extends Formatter { + + static final DefaultFormatter INSTANCE = new DefaultFormatter(); + + @Override + public String format(LogRecord record) { + String date = Constants.DEFAULT_DATE_FORMAT.format(new Date(record.getMillis())); + String log = String.format("[%s] [%s.%s/%s] %s%n", + date, record.getSourceClassName(), record.getSourceMethodName(), + record.getLevel().getName(), MessageFormat.format(record.getMessage(), record.getParameters()) + ); + ByteArrayOutputStream builder = new ByteArrayOutputStream(); + if (record.getThrown() != null) + try (PrintWriter writer = new PrintWriter(builder)) { + record.getThrown().printStackTrace(writer); + } + return log + builder.toString(); + } + + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/LowerCaseEnumTypeAdapterFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/LowerCaseEnumTypeAdapterFactory.java new file mode 100644 index 000000000..8737856fc --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/LowerCaseEnumTypeAdapterFactory.java @@ -0,0 +1,72 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; + +/** + * + * @author huangyuhui + */ +public final class LowerCaseEnumTypeAdapterFactory implements TypeAdapterFactory { + + public static final LowerCaseEnumTypeAdapterFactory INSTANCE = new LowerCaseEnumTypeAdapterFactory(); + + @Override + public TypeAdapter create(Gson gson, TypeToken tt) { + Class rawType = tt.getRawType(); + if (!rawType.isEnum()) + return null; + + HashMap lowercaseToConstant = new HashMap<>(); + for (Object constant : rawType.getEnumConstants()) + lowercaseToConstant.put(toLowercase(constant), (T) constant); + + return new TypeAdapter() { + @Override + public void write(JsonWriter writer, T t) throws IOException { + if (t == null) + writer.nullValue(); + else + writer.value(toLowercase(t)); + } + + @Override + public T read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + return lowercaseToConstant.get(reader.nextString().toLowerCase()); + } + }; + } + + private static String toLowercase(Object o) { + return o.toString().toLowerCase(Locale.US); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ManagedProcess.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ManagedProcess.java new file mode 100644 index 000000000..4dbaa924e --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ManagedProcess.java @@ -0,0 +1,137 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * The managed process. + * + * @author huangyuhui + * @see org.jackhuang.hmcl.launch.ExitWaiter + * @see org.jackhuang.hmcl.launch.StreamPump + */ +public class ManagedProcess { + + private final Process process; + private final List commands; + private final Map properties = new HashMap<>(); + private final Queue lines = new ConcurrentLinkedQueue<>(); + private final List relatedThreads = new LinkedList<>(); + + /** + * Constructor. + * + * @param process the raw system process that this instance manages. + * @param commands the command line of {@code process}. + */ + public ManagedProcess(Process process, List commands) { + this.process = process; + this.commands = Collections.unmodifiableList(new ArrayList<>(commands)); + } + + /** + * The raw system process that this instance manages. + * + * @return process + */ + public Process getProcess() { + return process; + } + + /** + * The command line. + * + * @return the list of each part of command line separated by spaces. + */ + public List getCommands() { + return commands; + } + + /** + * To save some information you need. + */ + public Map getProperties() { + return properties; + } + + /** + * The (unmodifiable) standard output/error lines. + * If you want to add lines, use {@link #addLine} + * + * @see #addLine + */ + public Collection getLines() { + return Collections.unmodifiableCollection(lines); + } + + public void addLine(String line) { + lines.add(line); + } + + /** + * Add related thread. + * + * If a thread is monitoring this raw process, + * you are required to add the instance by this method. + */ + public void addRelatedThread(Thread thread) { + relatedThreads.add(thread); + } + + /** + * True if the managed process is running. + */ + public boolean isRunning() { + try { + process.exitValue(); + return true; + } catch (IllegalThreadStateException e) { + return false; + } + } + + /** + * The exit code of raw process. + */ + public int getExitCode() { + return process.exitValue(); + } + + /** + * Destroys the raw process and other related threads that are monitoring this raw process. + */ + public void stop() { + process.destroy(); + relatedThreads.forEach(Thread::interrupt); + } + + @Override + public String toString() { + return "ManagedProcess[commands=" + commands + ", isRunning=" + isRunning() + "]"; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java new file mode 100644 index 000000000..e7d08fe20 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java @@ -0,0 +1,191 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; +import static org.jackhuang.hmcl.util.StringUtils.*; + +/** + * + * @author huangyuhui + */ +public final class NetworkUtils { + + private NetworkUtils() { + } + + private static final X509TrustManager XTM = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + private static final HostnameVerifier HNV = (a, b) -> true; + + private static volatile boolean initHttps = false; + + private static synchronized void initHttps() { + if (initHttps) + return; + + initHttps = true; + + System.setProperty("https.protocols", "SSLv3,TLSv1"); + try { + SSLContext c = SSLContext.getInstance("SSL"); + c.init(null, new X509TrustManager[] { XTM }, new SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(c.getSocketFactory()); + } catch (GeneralSecurityException e) { + } + HttpsURLConnection.setDefaultHostnameVerifier(HNV); + } + + private static Supplier userAgentSupplier = () -> RandomUserAgent.randomUserAgent(); + + public static String getUserAgent() { + return userAgentSupplier.get(); + } + + public static void setUserAgentSupplier(Supplier userAgentSupplier) { + Objects.requireNonNull(userAgentSupplier); + NetworkUtils.userAgentSupplier = userAgentSupplier; + } + + public static HttpURLConnection createConnection(URL url, Proxy proxy) throws IOException { + initHttps(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoInput(true); + connection.setUseCaches(false); + connection.setConnectTimeout(15000); + connection.setReadTimeout(15000); + connection.setRequestProperty("User-Agent", getUserAgent()); + return connection; + } + + public static String doGet(URL url) throws IOException { + return IOUtils.readFullyAsString(url.openConnection().getInputStream()); + } + + public static String doGet(URL url, Proxy proxy) throws IOException { + return IOUtils.readFullyAsString(createConnection(url, proxy).getInputStream()); + } + + public static String doPost(URL u, Map params) throws IOException { + StringBuilder sb = new StringBuilder(); + if (params != null) { + for (Map.Entry e : params.entrySet()) + sb.append(e.getKey()).append("=").append(e.getValue()).append("&"); + sb.deleteCharAt(sb.length() - 1); + } + return doPost(u, sb.toString()); + } + + public static String doPost(URL u, String post) throws IOException { + return doPost(u, post, "application/x-www-form-urlencoded"); + } + + public static String doPost(URL u, String post, String contentType) throws IOException { + return doPost(u, post, contentType, Proxy.NO_PROXY); + } + + public static String doPost(URL u, String post, String contentType, Proxy proxy) throws IOException { + byte[] bytes = post.getBytes(Charsets.UTF_8); + + HttpURLConnection con = createConnection(u, proxy); + con.setRequestMethod("POST"); + con.setDoOutput(true); + con.setRequestProperty("Content-Type", contentType + "; charset=utf-8"); + con.setRequestProperty("Content-Length", "" + bytes.length); + OutputStream os = null; + try { + os = con.getOutputStream(); + if (os != null) + os.write(bytes); + } finally { + IOUtils.closeQuietly(os); + } + return readData(con); + } + + public static String readData(HttpURLConnection con) throws IOException { + InputStream is = null; + try { + is = con.getInputStream(); + return IOUtils.readFullyAsString(is, Charsets.UTF_8); + } catch (IOException e) { + IOUtils.closeQuietly(is); + is = con.getErrorStream(); + if (is != null) + return IOUtils.readFullyAsString(is, Charsets.UTF_8); + throw e; + } finally { + IOUtils.closeQuietly(is); + } + } + + public static String detectFileName(URL url) throws IOException { + return detectFileName(url, Proxy.NO_PROXY); + } + + public static String detectFileName(URL url, Proxy proxy) throws IOException { + HttpURLConnection conn = createConnection(url, proxy); + conn.connect(); + if (conn.getResponseCode() / 100 != 2) + throw new IOException("Response code " + conn.getResponseCode()); + + return detectFileName(conn); + } + + public static String detectFileName(HttpURLConnection conn) { + String disposition = conn.getHeaderField("Content-Disposition"); + if (disposition == null || !disposition.contains("filename=")) { + String u = conn.getURL().toString(); + return substringAfterLast(u, '/'); + } else + return removeSurrounding(substringAfter(disposition, "filename="), "\""); + } + + public static URL toURL(String str) { + return Lang.invoke(() -> new URL(str)); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/OperatingSystem.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/OperatingSystem.java new file mode 100644 index 000000000..24ee1c16a --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/OperatingSystem.java @@ -0,0 +1,124 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.nio.charset.Charset; +import java.util.Locale; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; + +/** + * Represents the operating system. + * + * @author huangyuhui + */ +public enum OperatingSystem { + /** + * Microsoft Windows. + */ + WINDOWS("windows"), + /** + * Linux and Unix like OS, including Solaris. + */ + LINUX("linux"), + /** + * Mac OS X. + */ + OSX("osx"), + /** + * Unknown operating system. + */ + UNKNOWN("universal"); + + private final String checkedName; + + private OperatingSystem(String checkedName) { + this.checkedName = checkedName; + } + + public String getCheckedName() { + return checkedName; + } + + /** + * The current operating system. + */ + public static final OperatingSystem CURRENT_OS; + + /** + * The total memory/MB this computer have. + */ + public static final long TOTAL_MEMORY; + + /** + * The suggested memory size/MB for Minecraft to allocate. + */ + public static final long SUGGESTED_MEMORY; + + public static final String PATH_SEPARATOR = File.pathSeparator; + public static final String FILE_SEPARATOR = File.separator; + public static final String LINE_SEPARATOR = System.lineSeparator(); + + /** + * The system default encoding. + */ + public static final String ENCODING = System.getProperty("sun.jnu.encoding", Charset.defaultCharset().name()); + + /** + * The version of current operating system. + */ + public static final String SYSTEM_VERSION = System.getProperty("os.version"); + + /** + * The architecture of current operating system. + */ + public static final String SYSTEM_ARCHITECTURE; + + static { + String name = System.getProperty("os.name").toLowerCase(Locale.US); + if (name.contains("win")) + CURRENT_OS = WINDOWS; + else if (name.contains("mac")) + CURRENT_OS = OSX; + else if (name.contains("solaris") || name.contains("linux") || name.contains("unix") || name.contains("sunos")) + CURRENT_OS = LINUX; + else + CURRENT_OS = UNKNOWN; + + Object bytes = ReflectionHelper.call(ManagementFactory.getOperatingSystemMXBean(), "getTotalPhysicalMemorySize"); + if (bytes instanceof Long) + TOTAL_MEMORY = ((Long) bytes) / 1024 / 1024; + else + TOTAL_MEMORY = 1024; + + SUGGESTED_MEMORY = Math.round(1.0 * TOTAL_MEMORY / 4.0 / 128.0) * 128; + + String arch = System.getProperty("sun.arch.data.model"); + if (arch == null) + arch = System.getProperty("os.arch"); + SYSTEM_ARCHITECTURE = arch; + } + + public static void setClipboard(String string) { + ClipboardContent c = new ClipboardContent(); + c.putString(string); + Clipboard.getSystemClipboard().setContent(c); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Pair.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Pair.java new file mode 100644 index 000000000..e231465f4 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Pair.java @@ -0,0 +1,86 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.Map; +import java.util.Objects; + +/** + * + * @author huangyuhui + */ +public class Pair implements Map.Entry { + + private K key; + private V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + public void setKey(K key) { + this.key = key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + V original = this.value; + this.value = value; + return original; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + Objects.hashCode(this.key); + hash = 23 * hash + Objects.hashCode(this.value); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final Pair other = (Pair) obj; + if (!Objects.equals(this.key, other.key)) + return false; + if (!Objects.equals(this.value, other.value)) + return false; + return true; + } + + @Override + public String toString() { + return "(" + key + ", " + value + ")"; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Platform.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Platform.java new file mode 100644 index 000000000..44a4876b4 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Platform.java @@ -0,0 +1,102 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; + +/** + * The platform that indicates which the platform of operating system is, 64-bit or 32-bit. + * Of course, 128-bit and 16-bit is not supported. + * + * @author huangyuhui + */ +public enum Platform { + BIT_32("32"), + BIT_64("64"), + UNKNOWN("unknown"); + + private final String bit; + + private Platform(String bit) { + this.bit = bit; + } + + public String getBit() { + return bit; + } + + /** + * True if current Java Environment is 64-bit. + */ + public static final boolean IS_64_BIT = OperatingSystem.SYSTEM_ARCHITECTURE.contains("64"); + + /** + * The platform of current Java Environment. + */ + public static final Platform PLATFORM = IS_64_BIT ? BIT_64 : BIT_32; + + /** + * The json serializer to {@link Platform}. + */ + public static class Serializer implements JsonSerializer, JsonDeserializer { + + public static final Serializer INSTANCE = new Serializer(); + + private Serializer() { + } + + @Override + public JsonElement serialize(Platform t, Type type, JsonSerializationContext jsc) { + if (t == null) + return null; + else + switch (t) { + case BIT_32: + return new JsonPrimitive(0); + case BIT_64: + return new JsonPrimitive(1); + default: + return new JsonPrimitive(-1); + } + } + + @Override + public Platform deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { + if (je == null) + return null; + else + switch (je.getAsInt()) { + case 0: + return BIT_32; + case 1: + return BIT_64; + default: + return UNKNOWN; + } + } + + } + +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/AutoTypingMap.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Properties.java similarity index 60% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/AutoTypingMap.kt rename to HMCLCore/src/main/java/org/jackhuang/hmcl/util/Properties.java index 704d0d549..9984397e0 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/AutoTypingMap.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Properties.java @@ -15,24 +15,24 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.util +package org.jackhuang.hmcl.util; + +import java.util.concurrent.atomic.AtomicReference; +import javafx.beans.property.Property; /** - * A map that support auto casting. + * + * @author huangyuhui */ -class AutoTypingMap(private val impl: MutableMap) { +public final class Properties { - fun clear() = impl.clear() - - @Suppress("UNCHECKED_CAST") - operator fun get(key: K): V = impl[key] as V - operator fun set(key: K, value: Any?) { - if (value != null) - impl[key] = value + private Properties() { } - val values get() = impl.values - val keys get() = impl.keys - fun containsKey(key: K) = impl.containsKey(key) - fun remove(key: K) = impl.remove(key) -} \ No newline at end of file + public static void updateAsync(Property property, T newValue, AtomicReference update) { + if (update.getAndSet(newValue) == null) + Constants.UI_THREAD_SCHEDULER.accept(() -> { + property.setValue(update.getAndSet(null)); + }); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/RandomUserAgent.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/RandomUserAgent.java new file mode 100644 index 000000000..3795779e1 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/RandomUserAgent.java @@ -0,0 +1,1664 @@ +package org.jackhuang.hmcl.util; + +import java.util.HashMap; +import java.util.Map; + +public class RandomUserAgent { + + private static final HashMap uaMap = new HashMap<>(); + private static final HashMap freqMap = new HashMap<>(); + + static { + + freqMap.put("Internet Explorer", 11.8); + freqMap.put("Firefox", 28.2); + freqMap.put("Chrome", 52.9); + freqMap.put("Safari", 3.9); + freqMap.put("Opera", 1.8); + + uaMap.put("Internet Explorer", new String[] { + "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0", + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)", + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)", + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/4.0; InfoPath.2; SV1; .NET CLR 2.0.50727; WOW64)", + "Mozilla/5.0 (compatible; MSIE 10.0; Macintosh; Intel Mac OS X 10_7_3; Trident/6.0)", + "Mozilla/4.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)", + "Mozilla/1.22 (compatible; MSIE 10.0; Windows 3.1)", + "Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))", + "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; Media Center PC 6.0; InfoPath.3; MS-RTC LM 8; Zune 4.7)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; Media Center PC 6.0; InfoPath.3; MS-RTC LM 8; Zune 4.7", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 4.0; InfoPath.3; MS-RTC LM 8; .NET4.0C; .NET4.0E)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; chromeframe/12.0.742.112)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 4.0; Tablet PC 2.0; InfoPath.3; .NET4.0C; .NET4.0E)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; yie8)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET CLR 1.1.4322; .NET4.0C; Tablet PC 2.0)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; FunWebProducts)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; chromeframe/13.0.782.215)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; chromeframe/11.0.696.57)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) chromeframe/10.0.648.205", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.1; SV1; .NET CLR 2.8.52393; WOW64; en-US)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0; chromeframe/11.0.696.57)", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/4.0; GTB7.4; InfoPath.3; SV1; .NET CLR 3.1.76908; WOW64; en-US)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.8.36217; WOW64; en-US)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; .NET CLR 2.7.58687; SLCC2; Media Center PC 5.0; Zune 3.4; Tablet PC 3.6; InfoPath.3)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; SLCC1; .NET CLR 1.1.4322)", + "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 3.0.04506.30)", + "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 5.0; Trident/4.0; FBSMTWB; .NET CLR 2.0.34861; .NET CLR 3.0.3746.3218; .NET CLR 3.5.33652; msn OptimizedIE8;ENUS)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.2; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; MS-RTC LM 8)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; MS-RTC LM 8", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; Media Center PC 6.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.3; .NET4.0C; .NET4.0E; .NET CLR 3.5.30729; .NET CLR 3.0.30729; MS-RTC LM 8)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 3.0)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; msn OptimizedIE8;ZHCN)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MS-RTC LM 8; InfoPath.3; .NET4.0C; .NET4.0E) chromeframe/8.0.552.224", + "Mozilla/4.0(compatible; MSIE 7.0b; Windows NT 6.0)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 6.0)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; Media Center PC 3.0; .NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.1)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; FDM; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; InfoPath.1)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; Alexa Toolbar; .NET CLR 2.0.50727)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; Alexa Toolbar)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.40607)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.0.3705; Media Center PC 3.1; Alexa Toolbar; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)", + "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; el-GR)", + "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 5.2)", + "Mozilla/5.0 (MSIE 7.0; Macintosh; U; SunOS; X11; gu; SV1; InfoPath.2; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)", + "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; c .NET CLR 3.0.04506; .NET CLR 3.5.30707; InfoPath.1; el-GR)", + "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; c .NET CLR 3.0.04506; .NET CLR 3.5.30707; InfoPath.1; el-GR)", + "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; fr-FR)", + "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; en-US)", + "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 5.2; WOW64; .NET CLR 2.0.50727)", + "Mozilla/5.0 (compatible; MSIE 7.0; Windows 98; SpamBlockerUtility 6.3.91; SpamBlockerUtility 6.2.91; .NET CLR 4.1.89;GB)", + "Mozilla/4.79 [en] (compatible; MSIE 7.0; Windows NT 5.0; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 1.1.4322; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)", + "Mozilla/4.0 (Windows; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", + "Mozilla/4.0 (Mozilla/4.0; MSIE 7.0; Windows NT 5.1; FDM; SV1; .NET CLR 3.0.04506.30)", + "Mozilla/4.0 (Mozilla/4.0; MSIE 7.0; Windows NT 5.1; FDM; SV1)", + "Mozilla/4.0 (compatible;MSIE 7.0;Windows NT 6.0)", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; Win64; x64; Trident/6.0; .NET4.0E; .NET4.0C)", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; SLCC2; .NET CLR 2.0.50727; InfoPath.3; .NET4.0C; .NET4.0E; .NET CLR 3.5.30729; .NET CLR 3.0.30729; MS-RTC LM 8)", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MS-RTC LM 8; .NET4.0C; .NET4.0E; InfoPath.3)", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; chromeframe/12.0.742.100)", + "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)", + "Mozilla/4.0 (compatible; MSIE 6.01; Windows NT 6.0)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.1; DigExt)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.1)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; YComp 5.0.2.6)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; YComp 5.0.0.0) (Compatible; ; ; Trident/4.0)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; YComp 5.0.0.0)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 4.0; .NET CLR 1.0.2914)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 4.0)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98; YComp 5.0.0.0)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98; Win 9x 4.90)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.1)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.3705)", + "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 4.0)", + "Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", + "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", + "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4325)", + "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)", + "Mozilla/45.0 (compatible; MSIE 6.0; Windows NT 5.1)", + "Mozilla/4.08 (compatible; MSIE 6.0; Windows NT 5.1)", + "Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)", + "Mozilla/4.0 (X11; MSIE 6.0; i686; .NET CLR 1.1.4322; .NET CLR 2.0.50727; FDM)", + "Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 6.0)", + "Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.2)", + "Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.0)", + "Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", + "Mozilla/4.0 (MSIE 6.0; Windows NT 5.1)", + "Mozilla/4.0 (MSIE 6.0; Windows NT 5.0)", + "Mozilla/4.0 (compatible;MSIE 6.0;Windows 98;Q312461)", + "Mozilla/4.0 (Compatible; Windows NT 5.1; MSIE 6.0) (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "Mozilla/4.0 (compatible; U; MSIE 6.0; Windows NT 5.1) (Compatible; ; ; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; U; MSIE 6.0; Windows NT 5.1)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3; Tablet PC 2.0)", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB6.5; QQDownload 534; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; SLCC2; .NET CLR 2.0.50727; Media Center PC 6.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729)", + "Mozilla/4.0 (compatible; MSIE 5.5b1; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.50; Windows NT; SiteKiosk 4.9; SiteCoach 1.0)", + "Mozilla/4.0 (compatible; MSIE 5.50; Windows NT; SiteKiosk 4.8; SiteCoach 1.0)", + "Mozilla/4.0 (compatible; MSIE 5.50; Windows NT; SiteKiosk 4.8)", + "Mozilla/4.0 (compatible; MSIE 5.50; Windows 98; SiteKiosk 4.8)", + "Mozilla/4.0 (compatible; MSIE 5.50; Windows 95; SiteKiosk 4.8)", + "Mozilla/4.0 (compatible;MSIE 5.5; Windows 98)", + "Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1)", + "Mozilla/4.0 (compatible; MSIE 5.5;)", + "Mozilla/4.0 (Compatible; MSIE 5.5; Windows NT5.0; Q312461; SV1; .NET CLR 1.1.4322; InfoPath.2)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT5)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 6.1; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 6.1; chromeframe/12.0.742.100; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30618)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.5)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.2; .NET CLR 1.1.4322; InfoPath.2; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; FDM)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.2; .NET CLR 1.1.4322) (Compatible; ; ; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.2; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)", + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)", + "Mozilla/4.0 (compatible; MSIE 5.23; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.22; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.21; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.2; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.2; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.17; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.17; Mac_PowerPC Mac OS; en)", + "Mozilla/4.0 (compatible; MSIE 5.16; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.16; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.15; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.15; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.14; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.13; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.12; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.12; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.05; Windows NT 4.0)", + "Mozilla/4.0 (compatible; MSIE 5.05; Windows NT 3.51)", + "Mozilla/4.0 (compatible; MSIE 5.05; Windows 98; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; YComp 5.0.0.0)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; Hotbar 4.1.8.0)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; DigExt)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; .NET CLR 1.0.3705)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.6; MSIECrawler)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.6; Hotbar 4.2.8.0)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.6; Hotbar 3.0)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.6)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.4)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.0.0; Hotbar 4.1.8.0)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.0.0)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Wanadoo 5.6)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Wanadoo 5.3; Wanadoo 5.5)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Wanadoo 5.1)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; SV1; .NET CLR 1.1.4322; .NET CLR 1.0.3705; .NET CLR 2.0.50727)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; SV1)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Q312461; T312461)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Q312461)", + "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; MSIECrawler)", + "Mozilla/4.0 (compatible; MSIE 5.0b1; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)", + "Mozilla/4.0(compatible; MSIE 5.0; Windows 98; DigExt)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT;)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; YComp 5.0.2.6)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; YComp 5.0.2.5)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; YComp 5.0.0.0)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; Hotbar 4.1.8.0)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; Hotbar 3.0)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; .NET CLR 1.0.3705)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 6.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.0.04506.648; .NET4.0C; .NET4.0E)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.9; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.2; .NET CLR 1.1.4322)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98;)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; YComp 5.0.2.4)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; Hotbar 3.0)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt; YComp 5.0.2.6; yplus 1.0)", + "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt; YComp 5.0.2.6)", + "Mozilla/4.0 (compatible; MSIE 4.5; Windows NT 5.1; .NET CLR 2.0.40607)", + "Mozilla/4.0 (compatible; MSIE 4.5; Windows 98; )", + "Mozilla/4.0 (compatible; MSIE 4.5; Mac_PowerPC)", + "Mozilla/4.0 (compatible; MSIE 4.5; Mac_PowerPC)", + "Mozilla/4.0 PPC (compatible; MSIE 4.01; Windows CE; PPC; 240x320; Sprint:PPC-6700; PPC; 240x320)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows NT)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows NT 5.0)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint;PPC-i830; PPC; 240x320)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint; SCH-i830; PPC; 240x320)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:SPH-ip830w; PPC; 240x320)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:SPH-ip320; Smartphone; 176x220)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:SCH-i830; PPC; 240x320)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:SCH-i320; Smartphone; 176x220)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:PPC-i830; PPC; 240x320)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Smartphone; 176x220)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320; Sprint:PPC-6700; PPC; 240x320)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320; PPC)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98; Hotbar 3.0)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98; DigExt)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)", + "Mozilla/4.0 (compatible; MSIE 4.01; Windows 95)", + "Mozilla/4.0 (compatible; MSIE 4.01; Mac_PowerPC)", + "Mozilla/4.0 WebTV/2.6 (compatible; MSIE 4.0)", + "Mozilla/4.0 (compatible; MSIE 4.0; Windows NT)", + "Mozilla/4.0 (compatible; MSIE 4.0; Windows 98 )", + "Mozilla/4.0 (compatible; MSIE 4.0; Windows 95; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "Mozilla/4.0 (compatible; MSIE 4.0; Windows 95)", + "Mozilla/4.0 (Compatible; MSIE 4.0)", + "Mozilla/2.0 (compatible; MSIE 4.0; Windows 98)", + "Mozilla/2.0 (compatible; MSIE 3.03; Windows 3.1)", + "Mozilla/2.0 (compatible; MSIE 3.02; Windows 3.1)", + "Mozilla/2.0 (compatible; MSIE 3.01; Windows 95)", + "Mozilla/2.0 (compatible; MSIE 3.01; Windows 95)", + "Mozilla/2.0 (compatible; MSIE 3.0B; Windows NT)", + "Mozilla/3.0 (compatible; MSIE 3.0; Windows NT 5.0)", + "Mozilla/2.0 (compatible; MSIE 3.0; Windows 95)", + "Mozilla/2.0 (compatible; MSIE 3.0; Windows 3.1)", + "Mozilla/4.0 (compatible; MSIE 2.0; Windows NT 5.0; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)", + "Mozilla/1.22 (compatible; MSIE 2.0; Windows 3.1)" }); + + uaMap.put("Firefox", new String[] { + "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20120101 Firefox/29.0", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0", + "Mozilla/5.0 (X11; OpenBSD amd64; rv:28.0) Gecko/20100101 Firefox/28.0", + "Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0", + "Mozilla/5.0 (Windows NT 6.1; rv:27.3) Gecko/20130101 Firefox/27.3", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:27.0) Gecko/20121011 Firefox/27.0", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:25.0) Gecko/20100101 Firefox/25.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0", + "Mozilla/5.0 (Windows NT 6.0; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0", + "Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/23.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20130406 Firefox/23.0", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0", + "Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/22.0", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:22.0) Gecko/20130328 Firefox/22.0", + "Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.0", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:21.0.0) Gecko/20121011 Firefox/21.0.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20100101 Firefox/21.0", + "Mozilla/5.0 (X11; Linux i686; rv:21.0) Gecko/20100101 Firefox/21.0", + "Mozilla/5.0 (Windows NT 6.2; rv:21.0) Gecko/20130326 Firefox/21.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130401 Firefox/21.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130331 Firefox/21.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130330 Firefox/21.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0", + "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130401 Firefox/21.0", + "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130328 Firefox/21.0", + "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0", + "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130401 Firefox/21.0", + "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130331 Firefox/21.0", + "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20100101 Firefox/21.0", + "Mozilla/5.0 (Windows NT 5.0; rv:21.0) Gecko/20100101 Firefox/21.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64;) Gecko/20100101 Firefox/20.0", + "Mozilla/5.0 (Windows x86; rv:19.0) Gecko/20100101 Firefox/19.0", + "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/19.0", + "Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20100101 Firefox/18.0.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0.6", + "Mozilla/5.0 (X11; Ubuntu; Linux armv7l; rv:17.0) Gecko/20100101 Firefox/17.0", + "Mozilla/6.0 (Windows NT 6.2; WOW64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1", + "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1", + "Mozilla/5.0 (X11; NetBSD amd64; rv:16.0) Gecko/20121102 Firefox/16.0", + "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.16) Gecko/20120427 Firefox/15.0a1", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20120427 Firefox/15.0a1", + "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:15.0) Gecko/20120910144328 Firefox/15.0.2", + "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:15.0) Gecko/20121011 Firefox/15.0.1", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:14.0) Gecko/20120405 Firefox/14.0a1", + "Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20120405 Firefox/14.0a1", + "Mozilla/5.0 (Windows NT 5.1; rv:14.0) Gecko/20120405 Firefox/14.0a1", + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1", + "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:14.0) Gecko/20100101 Firefox/14.0.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; WOW64; en-US; rv:2.0.4) Gecko/20120718 AskTbAVR-IDW/3.12.5.17700 Firefox/14.0.1", + "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/14.0.1", + "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/ 20120405 Firefox/14.0.1", + "Mozilla/5.0 (Windows NT 6.0; rv:14.0) Gecko/20100101 Firefox/14.0.1", + "Mozilla/5.0 (Windows NT 5.1; rv:15.0) Gecko/20100101 Firefox/13.0.1", + "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/12.0", + "Mozilla/5.0 (Windows NT 6.1; de;rv:12.0) Gecko/20120403211507 Firefox/12.0", + "Mozilla/5.0 (Windows NT 5.1; rv:12.0) Gecko/20120403211507 Firefox/12.0", + "Mozilla/5.0 (compatible; Windows; U; Windows NT 6.2; WOW64; en-US; rv:12.0) Gecko/20120403211507 Firefox/12.0", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.16) Gecko/20120421 Gecko Firefox/11.0", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.16) Gecko/20120421 Firefox/11.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:11.0) Gecko Firefox/11.0", + "Mozilla/5.0 (Windows NT 6.1; U;WOW64; de;rv:11.0) Gecko Firefox/11.0", + "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0", + "Mozilla/6.0 (Macintosh; I; Intel Mac OS X 11_7_9; de-LI; rv:1.9b4) Gecko/2012010317 Firefox/10.0a4", + "Mozilla/5.0 (Macintosh; I; Intel Mac OS X 11_7_9; de-LI; rv:1.9b4) Gecko/2012010317 Firefox/10.0a4", + "Mozilla/5.0 (X11; Mageia; Linux x86_64; rv:10.0.9) Gecko/20100101 Firefox/10.0.9", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0a2) Gecko/20111101 Firefox/9.0a2", + "Mozilla/5.0 (Windows NT 6.2; rv:9.0.1) Gecko/20100101 Firefox/9.0.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0", + "Mozilla/5.0 (Windows NT 5.1; rv:8.0; en_us) Gecko/20100101 Firefox/8.0", + "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/7.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110613 Firefox/6.0a2", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110612 Firefox/6.0a2", + "Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0", + "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20110814 Firefox/6.0", + "Mozilla/5.0 (Windows NT 5.1; rv:6.0) Gecko/20100101 Firefox/6.0 FirePHP/0.6", + "Mozilla/5.0 (Windows NT 5.0; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0", + "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:5.0a2) Gecko/20110524 Firefox/5.0a2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.28) Gecko/20120306 Firefox/5.0.1", + "Mozilla/5.0 (Windows NT 6.1; U; ru; rv:5.0.1.6) Gecko/20110501 Firefox/5.0.1 Firefox/5.0.1", + "mozilla/3.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/5.0.1", + "Mozilla/5.0 (X11; U; Linux i586; de; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (X11; U; Linux amd64; rv:5.0) Gecko/20100101 Firefox/5.0 (Debian)", + "Mozilla/5.0 (X11; U; Linux amd64; en-US; rv:5.0) Gecko/20110619 Firefox/5.0", + "Mozilla/5.0 (X11; Linux) Gecko Firefox/5.0", + "Mozilla/5.0 (X11; Linux x86_64; rv:5.0) Gecko/20100101 Firefox/5.0 FirePHP/0.5", + "Mozilla/5.0 (X11; Linux x86_64; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0", + "Mozilla/5.0 (X11; Linux x86_64) Gecko Firefox/5.0", + "Mozilla/5.0 (X11; Linux ppc; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (X11; Linux AMD64) Gecko Firefox/5.0", + "Mozilla/5.0 (X11; FreeBSD amd64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:5.0) Gecko/20110619 Firefox/5.0", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 6.1.1; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.2; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.1; U; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.0; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.0; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.2a1pre) Gecko/20110324 Firefox/4.2a1pre", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.2a1pre) Gecko/20100101 Firefox/4.2a1pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110324 Firefox/4.2a1pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110323 Firefox/4.2a1pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110208 Firefox/4.2a1pre", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.0b9pre) Gecko/20110111 Firefox/4.0b9pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b9pre) Gecko/20101228 Firefox/4.0b9pre", + "Mozilla/5.0 (Windows NT 5.1; rv:2.0b9pre) Gecko/20110105 Firefox/4.0b9pre", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b8pre) Gecko/20101114 Firefox/4.0b8pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101213 Firefox/4.0b8pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101128 Firefox/4.0b8pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101114 Firefox/4.0b8pre", + "Mozilla/5.0 (Windows NT 5.1; rv:2.0b8pre) Gecko/20101127 Firefox/4.0b8pre", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8", + "Mozilla/4.0 (compatible; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8)", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b7pre) Gecko/20100921 Firefox/4.0b7pre", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b7) Gecko/20101111 Firefox/4.0b7", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b7) Gecko/20100101 Firefox/4.0b7", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre Firefox/4.0b6pre", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.0b4) Gecko/20100818 Firefox/4.0b4", + "Mozilla/5.0 (X11; Linux i686; rv:2.0b3pre) Gecko/20100731 Firefox/4.0b3pre", + "Mozilla/5.0 (Windows NT 5.2; rv:2.0b13pre) Gecko/20110304 Firefox/4.0b13pre", + "Mozilla/5.0 (Windows NT 5.1; rv:2.0b13pre) Gecko/20110223 Firefox/4.0b13pre", + "Mozilla/5.0 (X11; Linux i686; rv:2.0b12pre) Gecko/20110204 Firefox/4.0b12pre", + "Mozilla/5.0 (X11; Linux i686; rv:2.0b12pre) Gecko/20100101 Firefox/4.0b12pre", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b11pre) Gecko/20110128 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110131 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110129 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110128 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b11pre) Gecko/20110126 Firefox/4.0b11pre", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b11pre) Gecko/20110126 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b10pre) Gecko/20110118 Firefox/4.0b10pre", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b10pre) Gecko/20110113 Firefox/4.0b10pre", + "Mozilla/5.0 (X11; Linux i686; rv:2.0b10) Gecko/20100101 Firefox/4.0b10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:2.0b10) Gecko/20110126 Firefox/4.0b10", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b10) Gecko/20110126 Firefox/4.0b10", + "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.1.3) Gecko/20091020 Ubuntu/10.04 (lucid) Firefox/4.0.1", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.0.1) Gecko/20110506 Firefox/4.0.1", + "Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20110518 Firefox/4.0.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:2.0.1) Gecko/20110606 Firefox/4.0.1", + "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:2.0) Gecko/20110307 Firefox/4.0", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:2.0) Gecko/20110404 Fedora/16-dev Firefox/4.0", + "Mozilla/5.0 (X11; Arch Linux i686; rv:2.0) Gecko/20110321 Firefox/4.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0) Gecko/20110319 Firefox/4.0", + "Mozilla/5.0 (Windows NT 6.1; rv:1.9) Gecko/20100101 Firefox/4.0", + "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/20121223 Ubuntu/9.25 (jaunty) Firefox/3.8", + "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", + "Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20100401 Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", + "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.3a5pre) Gecko/20100526 Firefox/3.7a5pre", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2) Gecko/20091218 Firefox 3.6b5", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2a1pre) Gecko/20090428 Firefox/3.6a1pre", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2a1pre) Gecko/20090405 Firefox/3.6a1pre", + "Mozilla/5.0 (X11; U; Linux i686; ru-RU; rv:1.9.2a1pre) Gecko/20090405 Ubuntu/9.04 (jaunty) Firefox/3.6a1pre", + "Mozilla/5.0 (Windows; Windows NT 5.1; es-ES; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre", + "Mozilla/5.0 (Windows; Windows NT 5.1; en-US; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.9) Gecko/20100915 Gentoo Firefox/3.6.9", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.9) Gecko/20100827 Red Hat/3.6.9-2.el6 Firefox/3.6.9", + "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.9.2.9) Gecko/20100913 Firefox/3.6.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; rv:1.9.2.9) Gecko/20100913 Firefox/3.6.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9 ( .NET CLR 3.5.30729; .NET CLR 4.0.20506)", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6;en-US; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9", + "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.2.8) Gecko/20101230 Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100804 Gentoo Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100723 SUSE/3.6.8-0.1.1 Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.2.8) Gecko/20100722 Ubuntu/10.04 (lucid) Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; fi-FI; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.8) Gecko/20100727 Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; de-DE; rv:1.9.2.8) Gecko/20100725 Gentoo Firefox/3.6.8", + "Mozilla/5.0 (X11; U; FreeBSD i386; de-CH; rv:1.9.2.8) Gecko/20100729 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.8) Gecko/20100722 AskTbADAP/3.9.1.14019 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.8) Gecko/20100722 Firefox 3.6.8 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0C)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.8) Gecko/20100722 Firefox 3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.3) Gecko/20121221 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-TW; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100809 Fedora/3.6.7-1.fc14 Firefox/3.6.7", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100723 Fedora/3.6.7-1.fc13 Firefox/3.6.7", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.7) Gecko/20100726 CentOS/3.6-3.el5.centos Firefox/3.6.7", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.7 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-PT; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.0", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-PT; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; de-AT; rv:1.9.1.8) Gecko/20100625 Firefox/3.6.6", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.4) Gecko/20100614 Ubuntu/10.04 (lucid) Firefox/3.6.4", + "Mozilla/5.0 (X11; U; Linux i686; fa; rv:1.8.1.4) Gecko/20100527 Firefox/3.6.4", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.4) Gecko/20100625 Gentoo Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-TW; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-CA; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.0 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100503 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; nb-NO; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3pre) Gecko/20100405 Firefox/3.6.3plugin1 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; he; rv:1.9.1b4pre) Gecko/20100405 Firefox/3.6.3plugin1", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.3) Gecko/20100403 Fedora/3.6.3-4.fc13 Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100403 Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.3) Gecko/20100401 SUSE/3.6.3-1.1 Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux i686; ko-KR; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100404 Ubuntu/10.04 (lucid) Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux AMD64; en-US; rv:1.9.2.3) Gecko/20100403 Ubuntu/10.10 (maverick) Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; pl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.0 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ca; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.2.28) Gecko/20120306 Firefox/3.6.28", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.28) Gecko/20120306 AskTbSTC-SRS/3.13.1.18132 Firefox/3.6.28 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.28) Gecko/20120306 Firefox/3.6.28 ( .NET CLR 3.5.30729; .NET4.0C)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2.25) Gecko/20111212 Firefox/3.6.25 ( .NET CLR 3.5.30729; .NET4.0C)", + "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.24) Gecko/20111101 SUSE/3.6.24-0.2.1 Firefox/3.6.24", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.24) Gecko/20111103 Firefox/3.6.24", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.24) Gecko/20111103 Firefox/3.6.24", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; fr; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; en-US; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; it; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.21) Gecko/20110830 Ubuntu/10.10 (maverick) Firefox/3.6.21", + "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.20) Gecko/20110805 Ubuntu/10.04 (lucid) Firefox/3.6.20", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.20) Gecko/20110804 Red Hat/3.6-2.el5 Firefox/3.6.20", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; hu; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.20) Gecko/20110803 AskTbFWV5/3.13.0.17701 Firefox/3.6.20 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; cs; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 GTB7.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.2) Gecko/20100316 AskTbSPC2/3.9.1.14019 Firefox/3.6.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 GTB6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.0.04506.648)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.0.04506.30)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.7; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.10pre) Gecko/20100902 Ubuntu/9.10 (karmic) Firefox/3.6.1pre", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.19", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; en-GB; rv:1.9.2.19) Gecko/20110707 Firefox/3.6.19", + "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", + "Mozilla/5.0 (X11; U; Linux i686; pl; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.18) Gecko/20110615 Ubuntu/10.10 (maverick) Firefox/3.6.18", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ar; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; pt-BR; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-GB; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", + "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:5.0) Gecko/20100101 Firefox/3.6.17 Firefox/3.6.17", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; sl; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (X11; U; Linux x86_64; ja-JP; rv:1.9.2.16) Gecko/20110323 Ubuntu/10.10 (maverick) Firefox/3.6.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16) Gecko/20110323 Ubuntu/9.10 (karmic) Firefox/3.6.16 FirePHP/0.5", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en; rv:1.9.1.13) Gecko/20100914 Firefox/3.6.16", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.16) Gecko/20110319 AskTbUTR/3.11.3.15590 Firefox/3.6.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16pre) Gecko/20110304 Ubuntu/10.10 (maverick) Firefox/3.6.15pre", + "Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.9.2.15) Gecko/20110303 Ubuntu/8.04 (hardy) Firefox/3.6.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.15) Gecko/20110303 Ubuntu/10.04 (lucid) Firefox/3.6.15 FirePHP/0.5", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.15) Gecko/20110330 CentOS/3.6-1.el5.centos Firefox/3.6.15", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C) FirePHP/0.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.15) Gecko/20110303 AskTbBT4/3.11.3.15590 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.14pre) Gecko/20110105 Firefox/3.6.14pre", + "Mozilla/5.0 (X11; U; Linux armv7l; en-US; rv:1.9.2.14) Gecko/20110224 Firefox/3.6.14 MB860/Version.0.43.3.MB860.AmericaMovil.en.MX", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-AU; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14 GTB7.1 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.13) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; nb-NO; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.13) Gecko/20110103 Fedora/3.6.13-1.fc14 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101223 Gentoo Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101219 Gentoo Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101206 Red Hat/3.6-3.el4 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101206 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-NZ; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.13) Gecko/20101206 Ubuntu/9.10 (karmic) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.13) Gecko/20101206 Red Hat/3.6-2.el5 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; da-DK; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux MIPS32 1074Kf CPS QuadCore; en-US; rv:1.9.2.13) Gecko/20110103 Fedora/3.6.13-1.fc14 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; pt-BR; rv:1.9.2.13) Gecko/20101209 Fedora/3.6.13-1.fc13 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.2.13) Gecko/20101206 Ubuntu/9.10 (karmic) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.13) Gecko/20101209 CentOS/3.6-2.el5.centos Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.9.2.12) Gecko/20101030 Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; es-MX; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.04 (lucid) Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.2.12) Gecko/20101027 Fedora/3.6.12-1.fc13 Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.2.12) Gecko/20101026 SUSE/3.6.12-0.7.1 Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101102 Gentoo Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101102 Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux ppc; fr; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux i686; ko-KR; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101114 Gentoo Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12 GTB7.1", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.12) Gecko/20101027 Fedora/3.6.12-1.fc13 Firefox/3.6.12", + "Mozilla/5.0 (X11; FreeBSD x86_64; rv:2.0) Gecko/20100101 Firefox/3.6.12", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; sv-SE; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 (.NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; .NET CLR 3.5.21022)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; de; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 GTB5", + "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.2.11) Gecko/20101028 CentOS/3.6-2.el5.centos Firefox/3.6.11", + "Mozilla/5.0 (X11; U; Linux armv7l; en-GB; rv:1.9.2.3pre) Gecko/20100723 Firefox/3.6.11", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; ru; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux x86_64; pt-BR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10 GTB7.1", + "Mozilla/5.0 (X11; U; Linux x86_64; el-GR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10 GTB7.1", + "Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; es-AR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.10) Gecko/20100915 Ubuntu/9.04 (jaunty) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.11) Gecko/20101013 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; en-CA; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100915 Ubuntu/9.10 (karmic) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100914 SUSE/3.6.10-0.3.1 Firefox/3.6.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ro; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; nl; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.1) Gecko/20100122 firefox/3.6.1", + "Mozilla/5.0(Windows; U; Windows NT 7.0; rv:1.9.2) Gecko/20100101 Firefox/3.6", + "Mozilla/5.0(Windows; U; Windows NT 5.2; rv:1.9.2) Gecko/20100101 Firefox/3.6", + "Mozilla/5.0 (X11; U; x86_64 Linux; en_GB, en_US; rv:1.9.2) Gecko/20100115 Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100222 Ubuntu/10.04 (lucid) Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100130 Gentoo Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2) Gecko/20100308 Ubuntu/10.04 (lucid) Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.2pre) Gecko/20100312 Ubuntu/9.04 (jaunty) Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100128 Gentoo Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100115 Ubuntu/10.04 (lucid) Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6 FirePHP/0.4", + "Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/3.6", + "Mozilla/5.0 (X11; FreeBSD i686) Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU; rv:1.9.2) Gecko/20100105 MRA 5.6 (build 03278) Firefox/3.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; lt; rv:1.9.2) Gecko/20100115 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.3a3pre) Gecko/20100306 Firefox3.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.8) Gecko/20100806 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.3) Gecko/20100401 Firefox/3.6;MEGAUPLOAD 1.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ar; rv:1.9.2) Gecko/20100115 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.2) Gecko/20100115 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b5pre) Gecko/20090517 Firefox/3.5b4pre (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Gentoo Linux x86_64; pl-PL) Gecko Firefox", + "Mozilla/5.0 (X11; ; Linux x86_64; rv:1.8.1.6) Gecko/20070802 Firefox", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.6) Gecko/2009011913 Firefox", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE; rv:1.9.2.20) Gecko/20110803 Firefox", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; rv:1.8.1.16) Gecko/20080702 Firefox", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.13) Gecko/20080313 Firefox" }); + + uaMap.put("Chrome", new String[] { + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36", + "Mozilla/5.0 (X11; CrOS i686 4319.74.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1467.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1500.55 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.90 Safari/537.36", + "Mozilla/5.0 (X11; NetBSD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36", + "Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.14 (KHTML, like Gecko) Chrome/24.0.1292.0 Safari/537.14", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1284.0 Safari/537.13", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.6 Safari/537.11", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.6 Safari/537.11", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.26 Safari/537.11", + "Mozilla/5.0 (Windows NT 6.0) yi; AppleWebKit/345667.12221 (KHTML, like Gecko) Chrome/23.0.1271.26 Safari/453667.1221", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.17 Safari/537.11", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_0) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", + "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.22 (KHTML, like Gecko) Chrome/19.0.1047.0 Safari/535.22", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.21 (KHTML, like Gecko) Chrome/19.0.1042.0 Safari/535.21", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.21 (KHTML, like Gecko) Chrome/19.0.1041.0 Safari/535.21", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/18.6.872.0 Safari/535.2 UNTRUSTED/1.0 3gpp-gba UNTRUSTED/1.0", + "Mozilla/5.0 (Macintosh; AMD Mac OS X 10_8_2) AppleWebKit/535.22 (KHTML, like Gecko) Chrome/18.6.872", + "Mozilla/5.0 (X11; CrOS i686 1660.57.0) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.46 Safari/535.19", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.45 Safari/535.19", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.45 Safari/535.19", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.45 Safari/535.19", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.151 Safari/535.19", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.19 (KHTML, like Gecko) Ubuntu/11.10 Chromium/18.0.1025.142 Chrome/18.0.1025.142 Safari/535.19", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.11 Safari/535.19", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/17.0.963.65 Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.04 Chromium/17.0.963.65 Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/10.10 Chromium/17.0.963.65 Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/17.0.963.65 Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (X11; FreeBSD amd64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.04 Chromium/17.0.963.56 Chrome/17.0.963.56 Safari/535.11", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.12 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/17.0.940.0 Safari/535.8", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.77 Safari/535.7ad-imcjapan-syosyaman-xkgi3lqg03!wgz", + "Mozilla/5.0 (X11; CrOS i686 1193.158.0) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7xs5D9rRDFpg2g", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.8", + "Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.6 (KHTML, like Gecko) Chrome/16.0.897.0 Safari/535.6", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.54 Safari/535.2", + "Mozilla/5.0 (X11; FreeBSD i386) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.121 Safari/535.2", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.10 Chromium/15.0.874.120 Chrome/15.0.874.120 Safari/535.2", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.120 Safari/535.2", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.872.0 Safari/535.2", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.04 Chromium/15.0.871.0 Chrome/15.0.871.0 Safari/535.2", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.864.0 Safari/535.2", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.860.0 Safari/535.2", + "Chrome/15.0.860.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/15.0.860.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.186 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.834.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.825.0 Chrome/14.0.825.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.824.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.815.10913 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.815.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.814.0 Chrome/14.0.814.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.814.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.813.0 Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.812.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.811.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.810.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.810.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.809.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.10 Chromium/14.0.808.0 Chrome/14.0.808.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.808.0 Chrome/14.0.808.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.804.0 Chrome/14.0.804.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.803.0 Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.801.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.801.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.794.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.794.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.790.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.790.0 Safari/535.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1) AppleWebKit/526.3 (KHTML, like Gecko) Chrome/14.0.564.21 Safari/526.3", + "Mozilla/5.0 (X11; CrOS i686 13.587.48) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.43 Safari/535.1", + "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41", + "Mozilla/5.0 ArchLinux (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/13.0.782.41 Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.32 Safari/535.1", + "Mozilla/5.0 (X11; Linux amd64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", + "Mozilla/5.0 (X11; CrOS i686 0.13.587) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.14 Safari/535.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.107 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.107 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.1 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.36 (KHTML, like Gecko) Chrome/13.0.766.0 Safari/534.36", + "Mozilla/5.0 (X11; Linux amd64) AppleWebKit/534.36 (KHTML, like Gecko) Chrome/13.0.766.0 Safari/534.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.35 (KHTML, like Gecko) Ubuntu/10.10 Chromium/13.0.764.0 Chrome/13.0.764.0 Safari/534.35", + "Mozilla/5.0 (X11; CrOS i686 0.13.507) AppleWebKit/534.35 (KHTML, like Gecko) Chrome/13.0.763.0 Safari/534.35", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.33 (KHTML, like Gecko) Ubuntu/9.10 Chromium/13.0.752.0 Chrome/13.0.752.0 Safari/534.33", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.31 (KHTML, like Gecko) Chrome/13.0.748.0 Safari/534.31", + "Mozilla/5.0 (Windows NT 6.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.750.0 Safari/534.30", + "Mozilla/5.0 (X11; CrOS i686 12.433.109) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.93 Safari/534.30", + "Mozilla/5.0 (X11; CrOS i686 12.0.742.91) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.93 Safari/534.30", + "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/12.0.742.91", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.91 Chromium/12.0.742.91 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30", + "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.60 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.53 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.113 Safari/534.30", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/11.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/11.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Windows NT 7.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Windows 8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; CrOS i686 12.433.216) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.105 Safari/534.30", + "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.724.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.25 (KHTML, like Gecko) Chrome/12.0.706.0 Safari/534.25", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.25 (KHTML, like Gecko) Chrome/12.0.704.0 Safari/534.25", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.703.0 Chrome/12.0.703.0 Safari/534.24", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.702.0 Chrome/12.0.702.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/12.0.702.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/12.0.702.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.700.3 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.699.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.699.0 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.698.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.697.0 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.71 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", + "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/11.0.696.50", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.43 Safari/534.24", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.14 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.12 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.12 Safari/534.24", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.04 Chromium/11.0.696.0 Chrome/11.0.696.0 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.694.0 Safari/534.24", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.23 (KHTML, like Gecko) Chrome/11.0.686.3 Safari/534.23", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.682.0 Safari/534.21", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_0; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", + "Mozilla/5.0 (Windows NT) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.669.0 Safari/534.20", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.19 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.19", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.18", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.660.0 Safari/534.18", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.654.0 Safari/534.17", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.652.0 Safari/534.17", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.82 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux armv7l; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", + "Mozilla/5.0 (X11; U; FreeBSD x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", + "Mozilla/5.0 (X11; U; FreeBSD i386; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.133 Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.133 Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.127 Chrome/10.0.648.127 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU; AppleWebKit/534.16; KHTML; like Gecko; Chrome/10.0.648.11;Safari/534.16)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.0 Chrome/10.0.648.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.0 Chrome/10.0.648.0 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.642.0 Chrome/10.0.642.0 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.639.0 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.638.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.634.0 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.634.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 SUSE/10.0.626.0 (KHTML, like Gecko) Chrome/10.0.626.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.613.0 Safari/534.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.613.0 Chrome/10.0.613.0 Safari/534.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.04 Chromium/10.0.612.3 Chrome/10.0.612.3 Safari/534.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.612.1 Safari/534.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.611.0 Chrome/10.0.611.0 Safari/534.15", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.602.0 Safari/534.14", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML,like Gecko) Chrome/9.1.0.0 Safari/540.0", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML, like Gecko) Ubuntu/10.10 Chrome/9.1.0.0 Safari/540.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Ubuntu/10.10 Chromium/9.0.600.0 Chrome/9.0.600.0 Safari/534.14", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.600.0 Safari/534.14", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.599.0 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-CA) AppleWebKit/534.13 (KHTML like Gecko) Chrome/9.0.597.98 Safari/534.13", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.84 Safari/534.13", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.44 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.19 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.107 Safari/534.13 v1333515017.9196", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.596.0 Safari/534.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Ubuntu/10.04 Chromium/9.0.595.0 Chrome/9.0.595.0 Safari/534.13", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Ubuntu/9.10 Chromium/9.0.592.0 Chrome/9.0.592.0 Safari/534.13", + "Mozilla/5.0 (X11; U; Windows NT 6; en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.587.0 Safari/534.12", + "Mozilla/5.0 (Windows U Windows NT 5.1 en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.583.0 Safari/534.12", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.579.0 Safari/534.12", + "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.576.0 Safari/534.12", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML, like Gecko) Ubuntu/10.10 Chrome/8.1.0.0 Safari/540.0", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.558.0 Safari/534.10", + "Mozilla/5.0 (X11; U; CrOS i686 0.9.130; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.344 Safari/534.10", + "Mozilla/5.0 (X11; U; CrOS i686 0.9.128; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.343 Safari/534.10", + "Mozilla/5.0 (X11; U; CrOS i686 0.9.128; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.341 Safari/534.10", + "Mozilla/5.0 (X11; U; CrOS i686 0.9.128; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.339 Safari/534.10", + "Mozilla/5.0 (X11; U; CrOS i686 0.9.128; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.339", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Ubuntu/10.10 Chromium/8.0.552.237 Chrome/8.0.552.237 Safari/534.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/533.3", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.210 Safari/534.10", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.200 Safari/534.10", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.551.0 Safari/534.10", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.548.0 Safari/534.10", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.544.0 Safari/534.10", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.15) Gecko/20101027 Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.540.0 Safari/534.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.540.0 Safari/534.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.540.0 Safari/534.10", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.540.0 Safari/534.10", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.9 (KHTML, like Gecko) Chrome/7.0.531.0 Safari/534.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.8 (KHTML, like Gecko) Chrome/7.0.521.0 Safari/534.8", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.24 Safari/534.7", + "Mozilla/5.0 (X11; U; Linux x86_64; fr-FR) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.500.0 Safari/534.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.498.0 Safari/534.6", + "Mozilla/5.0 (ipad Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.498.0 Safari/534.6", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/7.0.0 Safari/700.13", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.4 (KHTML, like Gecko) Chrome/6.0.481.0 Safari/534.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.53 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.33 Safari/534.3", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.470.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.464.0 Safari/534.3", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.464.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.463.0 Safari/534.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.462.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.462.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.460.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.460.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.460.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.459.0 Safari/534.3", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.0 Safari/534.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.457.0 Safari/534.3", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.456.0 Safari/534.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.454.0 Safari/534.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.454.0 Safari/534.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.453.1 Safari/534.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.453.1 Safari/534.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.453.1 Safari/534.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.451.0 Safari/534.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.1 SUSE/6.0.428.0 (KHTML, like Gecko) Chrome/6.0.428.0 Safari/534.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.428.0 Safari/534.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.428.0 Safari/534.1", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.428.0 Safari/534.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.427.0 Safari/534.1", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.422.0 Safari/534.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.417.0 Safari/534.1", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.416.0 Safari/534.1", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.414.0 Safari/534.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.9 (KHTML, like Gecko) Chrome/6.0.400.0 Safari/533.9", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.8 (KHTML, like Gecko) Chrome/6.0.397.0 Safari/533.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/6.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.999 Safari/533.4", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_0; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.86 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.86 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_0; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.86 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.70 Safari/533.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.127 Safari/533.4", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.126 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; fr-FR) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.126 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.125 Safari/533.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.370.0 Safari/533.4", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.368.0 Safari/533.4", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.366.2 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.366.0 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.366.0 Safari/533.4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.363.0 Safari/533.3", + "Mozilla/5.0 (X11; U; OpenBSD i386; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.359.0 Safari/533.3", + "Mozilla/5.0 (X11; U; x86_64 Linux; en_GB, en_US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.358.0 Safari/533.3", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.358.0 Safari/533.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.358.0 Safari/533.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.357.0 Safari/533.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.356.0 Safari/533.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.355.0 Safari/533.3", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.354.0 Safari/533.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.354.0 Safari/533.3", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.353.0 Safari/533.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.353.0 Safari/533.3", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.343.0 Safari/533.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.343.0 Safari/533.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_0; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.7 Safari/533.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.7 Safari/533.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.5 Safari/533.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.2 Safari/533.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.1 (KHTML, like Gecko) Chrome/5.0.335.0 Safari/533.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/533.16 (KHTML, like Gecko) Chrome/5.0.335.0 Safari/533.16", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.310.0 Safari/532.9", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.309.0 Safari/532.9", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.308.0 Safari/532.9", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_0; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.307.11 Safari/532.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.307.1 Safari/532.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.1.249.1025 Safari/532.5", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.8 (KHTML, like Gecko) Chrome/4.0.302.2 Safari/532.8", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.8 (KHTML, like Gecko) Chrome/4.0.288.1 Safari/532.8", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.8 (KHTML, like Gecko) Chrome/4.0.277.0 Safari/532.8", + "Mozilla/5.0 (X11; U; Slackware Linux x86_64; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.30 Safari/532.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; it-IT) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.25 Safari/532.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_8; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.246.0 Safari/532.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.4 (KHTML, like Gecko) Chrome/4.0.241.0 Safari/532.4", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.4 (KHTML, like Gecko) Chrome/4.0.237.0 Safari/532.4 Debian", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.3 (KHTML, like Gecko) Chrome/4.0.227.0 Safari/532.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.3 (KHTML, like Gecko) Chrome/4.0.224.2 Safari/532.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.3 (KHTML, like Gecko) Chrome/4.0.223.5 Safari/532.3", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.4 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.3 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE) Chrome/4.0.223.3 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.2 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.2 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.2 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.2 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.1 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.1 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.1 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.0 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.8 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.7 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.6 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.6 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.6 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.5 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.5 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.5 Safari/532.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.5 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.4 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.4 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.4 Safari/532.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.4 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.3 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.3 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.3 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.2 Safari/532.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.2 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.12 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.12 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.12 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.1 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.0 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.8 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.8 Safari/532.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.8 Safari/532.2", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.8 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.7 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.6 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.6 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.6 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.3 Safari/532.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.0 Safari/532.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.220.1 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.5 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.5 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.4 Safari/532.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.3 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.3 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.3 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.0 Safari/532.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.0 Safari/532.1", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.0 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.0 Safari/532.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.0 Safari/532.1", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_0; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.212.1 Safari/532.1", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.212.1 Safari/532.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.1", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.7 Safari/532.0", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.7 Safari/532.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.4 Safari/532.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.4 Safari/532.0", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.4 Safari/532.0", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.2 Safari/532.0" }); + + uaMap.put("Safari", new String[] { + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fi-fi) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es-es) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en_CA) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; tr-tr) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8_Adobe", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.9.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nb-no) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nb-no) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.9.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/417.9 (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.9.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/416.11 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nb-no) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13_Adobe", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/416.12 (KHTML, like Gecko) Safari/412.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-ca) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/416.11 (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13_Adobe", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.6", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5_Adobe", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS; pl-pl) AppleWebKit/412 (KHTML, like Gecko) Safari/412", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS; en-en) AppleWebKit/412 (KHTML, like Gecko) Safari/412", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/412 (KHTML, like Gecko) Safari/412", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/412 (KHTML, like Gecko) Safari/412", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es-ES) AppleWebKit/412 (KHTML, like Gecko) Safari/412", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en_US) AppleWebKit/412 (KHTML, like Gecko) Safari/412", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412 (KHTML, like Gecko) Safari/412 Privoxy/3.0", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412 (KHTML, like Gecko) Safari/412", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.6.2 (KHTML, like Gecko) Safari/412.2.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.6.2 (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412 (KHTML, like Gecko) Safari/412", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.6.2 (KHTML, like Gecko) Safari/412.2.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2_Adobe", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.6 (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412 (KHTML, like Gecko) Safari/412", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.6", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.6", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.8.1 (KHTML, like Gecko) Safari/312.6", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.6", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.6", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.3.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.8.1 (KHTML, like Gecko) Safari/312.6", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5_Adobe", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.5 (KHTML, like Gecko) Safari/312.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/312.5 (KHTML, like Gecko) Safari/312.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es-es) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.5 (KHTML, like Gecko) Safari/312.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/125", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5 (KHTML, like Gecko) Safari/312.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.1.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.1 (KHTML, like Gecko) Safari/125", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-ch) AppleWebKit/312.1.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-ca) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1 (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.1.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.1.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312.3.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-ch) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.11", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-ch) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-ch) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.11", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.5.7 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.11", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.7 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.5.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.11", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.7 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12_Adobe", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12_Adobe", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.5 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en_CA) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-au) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.4 (KHTML, like Gecko) Safari/100", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es-es) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.7", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-gb) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/85.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.7", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.7", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/124 (KHTML, like Gecko) Safari/125.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/124 (KHTML, like Gecko) Safari/125", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/124 (KHTML, like Gecko) Safari/125", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/124 (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/124 (KHTML, like Gecko) Safari/125.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/124 (KHTML, like Gecko) Safari/125", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85.8.2 (KHTML, like Gecko) Safari/85.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-gb) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/85.8.2 (KHTML, like Gecko) Safari/85.8.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.8.2 (KHTML, like Gecko) Safari/85.8", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.6", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.7", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092816 Mobile Safari 1.1.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/533+ (KHTML, like Gecko)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/528.8 (KHTML, like Gecko)", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.34 (KHTML, like Gecko) Dooble/1.40 Safari/534.34", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fi-fi) AppleWebKit/420+ (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.8.1 (KHTML, like Gecko) Safari/312.6", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/419.2 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-ch) AppleWebKit/85 (KHTML, like Gecko) Safari/85", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-CH) AppleWebKit/419.2 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; da-dk) AppleWebKit/522+ (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_6; en-us) AppleWebKit/528.16 (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; it-IT) AppleWebKit/521.25 (KHTML, like Gecko) Safari/521.24", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-us) AppleWebKit/419.2.1 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/522.11.1 (KHTML, like Gecko) Safari/419.3", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/521.32.1 (KHTML, like Gecko) Safari/521.32.1", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; es-es) AppleWebKit/531.22.7 (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-us) AppleWebKit/528.16 (KHTML, like Gecko)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_5; it-it) AppleWebKit/525.18 (KHTML, like Gecko)" }); + + uaMap.put("Opera", new String[] { + "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14", + "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.14", + "Opera/12.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.02", + "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00", + "Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00", + "Opera/12.0(Windows NT 5.2;U;en)Presto/22.9.168 Version/12.00", + "Opera/12.0(Windows NT 5.1;U;en)Presto/22.9.168 Version/12.00", + "Mozilla/5.0 (Windows NT 5.1) Gecko/20100101 Firefox/14.0 Opera/12.0", + "Opera/9.80 (Windows NT 6.1; WOW64; U; pt) Presto/2.10.229 Version/11.62", + "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.10.229 Version/11.62", + "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", + "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; de) Presto/2.9.168 Version/11.52", + "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.9.168 Version/11.51", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; de) Opera 11.51", + "Opera/9.80 (X11; Linux x86_64; U; fr) Presto/2.9.168 Version/11.50", + "Opera/9.80 (X11; Linux i686; U; hu) Presto/2.9.168 Version/11.50", + "Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 Version/11.11", + "Opera/9.80 (X11; Linux i686; U; es-ES) Presto/2.8.131 Version/11.11", + "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/5.0 Opera 11.11", + "Opera/9.80 (X11; Linux x86_64; U; bg) Presto/2.8.131 Version/11.10", + "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.8.99 Version/11.10", + "Opera/9.80 (Windows NT 5.1; U; zh-tw) Presto/2.8.131 Version/11.10", + "Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1", + "Opera/9.80 (X11; Linux x86_64; U; Ubuntu/10.10 (maverick); pl) Presto/2.7.62 Version/11.01", + "Opera/9.80 (X11; Linux i686; U; ja) Presto/2.7.62 Version/11.01", + "Opera/9.80 (X11; Linux i686; U; fr) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; sv) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 5.1; U;) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.7.62 Version/11.01", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101213 Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", + "Mozilla/5.0 (Windows NT 6.1; U; nl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01", + "Mozilla/5.0 (Windows NT 6.1; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; de) Opera 11.01", + "Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00", + "Opera/9.80 (X11; Linux i686; U; it) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.37 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; ko) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; en-GB) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1 x64; U; en) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.7.39 Version/11.00", + "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.7.39 Version/11.00", + "Opera/9.80 (Windows NT 5.1; U; MRA 5.5 (build 02842); ru) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 5.1; U; it) Presto/2.7.62 Version/11.00", + "Mozilla/5.0 (Windows NT 6.0; U; ja; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", + "Mozilla/5.0 (Windows NT 5.1; U; pl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", + "Mozilla/5.0 (Windows NT 5.1; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; fr) Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; ja) Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; en) Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; pl) Opera 11.00", + "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.6.31 Version/10.70", + "Mozilla/5.0 (Windows NT 5.2; U; ru; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.70", + "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.70", + "Opera/9.80 (Windows NT 5.2; U; zh-cn) Presto/2.6.30 Version/10.63", + "Opera/9.80 (Windows NT 5.2; U; en) Presto/2.6.30 Version/10.63", + "Opera/9.80 (Windows NT 5.1; U; MRA 5.6 (build 03278); ru) Presto/2.6.30 Version/10.63", + "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.6.30 Version/10.62", + "Mozilla/5.0 (X11; Linux x86_64; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.62", + "Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; de) Opera 10.62", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; en) Opera 10.62", + "Opera/9.80 (X11; Linux i686; U; pl) Presto/2.6.30 Version/10.61", + "Opera/9.80 (X11; Linux i686; U; es-ES) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows NT 6.0; U; it) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows 98; U; de) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Macintosh; Intel Mac OS X; U; nl) Presto/2.6.30 Version/10.61", + "Opera/9.80 (X11; Linux i686; U; en) Presto/2.5.27 Version/10.60", + "Opera/9.80 (Windows NT 6.0; U; nl) Presto/2.6.30 Version/10.60", + "Opera/10.60 (Windows NT 5.1; U; zh-cn) Presto/2.6.30 Version/10.60", + "Opera/10.60 (Windows NT 5.1; U; en-US) Presto/2.6.30 Version/10.60", + "Opera/9.80 (X11; Linux i686; U; it) Presto/2.5.24 Version/10.54", + "Opera/9.80 (X11; Linux i686; U; en-GB) Presto/2.5.24 Version/10.53", + "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", + "Mozilla/5.0 (Windows NT 5.1; U; Firefox/5.0; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", + "Mozilla/5.0 (Windows NT 5.1; U; Firefox/4.5; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", + "Mozilla/5.0 (Windows NT 5.1; U; Firefox/3.5; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; ko) Opera 10.53", + "Opera/9.80 (Windows NT 6.1; U; fr) Presto/2.5.24 Version/10.52", + "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.5.22 Version/10.51", + "Opera/9.80 (Windows NT 6.0; U; cs) Presto/2.5.22 Version/10.51", + "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.5.22 Version/10.51", + "Opera/9.80 (Linux i686; U; en) Presto/2.5.22 Version/10.51", + "Mozilla/5.0 (Windows NT 6.1; U; en-GB; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.51", + "Mozilla/5.0 (Linux i686; U; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.51", + "Mozilla/4.0 (compatible; MSIE 8.0; Linux i686; en) Opera 10.51", + "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 6.1; U; sk) Presto/2.6.22 Version/10.50", + "Opera/9.80 (Windows NT 6.1; U; ja) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 6.0; U; zh-cn) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 5.1; U; sk) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.5.22 Version/10.50", + "Opera/10.50 (Windows NT 6.1; U; en-GB) Presto/2.2.2", + "Opera/9.80 (S60; SymbOS; Opera Tablet/9174; U; en) Presto/2.7.81 Version/10.5", + "Opera/9.80 (X11; U; Linux i686; en-US; rv:1.9.2.3) Presto/2.2.15 Version/10.10", + "Opera/9.80 (X11; Linux x86_64; U; it) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 6.1; U; de) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 6.0; U; Gecko/20100115; pl) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 5.1; U; de) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.2.15 Version/10.10", + "Mozilla/5.0 (Windows NT 6.0; U; tr; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 10.10", + "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux i686; de) Opera 10.10", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0; tr) Opera 10.10", + "Opera/9.80 (X11; Linux x86_64; U; en-GB) Presto/2.2.15 Version/10.01", + "Opera/9.80 (X11; Linux x86_64; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux x86_64; U; de) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; ru) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; pt-BR) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; pl) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; nb) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; en-GB) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; Debian; pl) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; de) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; de) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.0; U; de) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 5.2; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.2.15 Version/10.00", + "Opera/9.99 (X11; U; sk)", + "Opera/9.99 (Windows NT 5.1; U; pl) Presto/9.9.9", + "Opera/9.80 (J2ME/MIDP; Opera Mini/5.0 (Windows; U; Windows NT 5.1; en) AppleWebKit/886; U; en) Presto/2.4.15", + "Opera/9.70 (Linux ppc64 ; U; en) Presto/2.2.1", + "Opera/9.70 (Linux i686 ; U; zh-cn) Presto/2.2.0", + "Opera/9.70 (Linux i686 ; U; en-us) Presto/2.2.0", + "Opera/9.70 (Linux i686 ; U; en) Presto/2.2.1", + "Opera/9.70 (Linux i686 ; U; en) Presto/2.2.0", + "Opera/9.70 (Linux i686 ; U; ; en) Presto/2.2.1", + "Opera/9.70 (Linux i686 ; U; ; en) Presto/2.2.1", + "Mozilla/5.0 (Linux i686 ; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.70", + "Mozilla/4.0 (compatible; MSIE 6.0; Linux i686 ; en) Opera 9.70", + "HTC_HD2_T8585 Opera/9.70 (Windows NT 5.1; U; de)", + "Opera 9.7 (Windows NT 5.2; U; en)", + "Opera/9.64(Windows NT 5.1; U; en) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; pl) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; hr) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; en-GB) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; en) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; de) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; cs) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; tr) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; sv) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; pl) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; nb) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; Linux Mint; nb) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; Linux Mint; it) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; en) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; de) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; da) Presto/2.1.1", + "Opera/9.64 (Windows NT 6.1; U; MRA 5.5 (build 02842); ru) Presto/2.1.1", + "Opera/9.64 (Windows NT 6.1; U; de) Presto/2.1.1", + "Opera/9.64 (Windows NT 6.0; U; zh-cn) Presto/2.1.1", + "Opera/9.64 (Windows NT 6.0; U; pl) Presto/2.1.1", + "Opera/9.63 (X11; Linux x86_64; U; ru) Presto/2.1.1", + "Opera/9.63 (X11; Linux x86_64; U; cs) Presto/2.1.1" }); + } + + public static String randomUserAgent() { + double rand = Math.random() * 100; + String browser = null; + double count = 0.0; + for (Map.Entry entry : freqMap.entrySet()) { + count += entry.getValue(); + if (rand <= count) { + browser = entry.getKey(); + break; + } + } + + if (browser == null) + browser = "Chrome"; + + String[] userAgents = uaMap.get(browser); + return userAgents[(int) Math.floor(Math.random() * userAgents.length)]; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ReflectionHelper.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ReflectionHelper.java new file mode 100644 index 000000000..17c9a1483 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ReflectionHelper.java @@ -0,0 +1,153 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import sun.misc.Unsafe; + +/** + * + * @author huangyuhui + */ +public final class ReflectionHelper { + + private static final Unsafe UNSAFE; + private static final long OBJECT_FIELD_OFFSET; + private static final Map> PRIMITIVES; + + static { + try { + UNSAFE = (Unsafe) AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + return theUnsafe.get(null); + }); + + OBJECT_FIELD_OFFSET = UNSAFE.objectFieldOffset(getField(AccessibleObject.class, "override")); + + } catch (PrivilegedActionException ex) { + throw new AssertionError(ex); + } + + PRIMITIVES = Lang.mapOf( + new Pair("byte", Byte.class), + new Pair("short", Short.class), + new Pair("int", Integer.class), + new Pair("long", Long.class), + new Pair("char", Character.class), + new Pair("float", Float.class), + new Pair("double", Double.class), + new Pair("boolean", Boolean.class) + ); + } + + private static void setAccessibleForcibly(AccessibleObject object) { + UNSAFE.putBoolean(object, OBJECT_FIELD_OFFSET, true); + } + + public static Method getMethod(Object object, String name) { + return getMethod(object.getClass(), name); + } + + public static Method getMethod(Class clazz, String name) { + try { + Method m = clazz.getDeclaredMethod(name); + setAccessibleForcibly(m); + return m; + } catch (Exception e) { + return null; + } + } + + public static Field getField(Object object, String name) { + return getField(object.getClass(), name); + } + + public static Field getField(Class clazz, String name) { + try { + Field f = clazz.getDeclaredField(name); + setAccessibleForcibly(f); + return f; + } catch (Exception e) { + return null; + } + } + + public static Object call(Class cls, String name, Object object, Object... args) { + try { + if (args.length == 0) + try { + return cls.getDeclaredField(name).get(object); + } catch (NoSuchFieldException ignored) { + } + if ("new".equals(name)) { + for (Constructor c : cls.getDeclaredConstructors()) + if (checkParameter(c, args)) + return c.newInstance(args); + } else + return forMethod(cls, name, args).get().invoke(object, args); + throw new RuntimeException(); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot find '" + name + "' in class '" + cls.getName() + "'", e); + } + } + + public static Object call(Object obj, String name, Object... args) { + return call(obj.getClass(), name, obj, args); + } + + public static boolean checkParameter(Executable exec, Object... args) { + Class[] cArgs = exec.getParameterTypes(); + if (args.length == cArgs.length) { + for (int i = 0; i < args.length; ++i) { + Object arg = args[i]; + if (arg != null ? !isInstance(cArgs[i], arg) : cArgs[i].isPrimitive()) + return false; + } + setAccessibleForcibly(exec); + return true; + } else + return false; + } + + public static boolean isInstance(Class superClass, Object obj) { + if (superClass.isInstance(obj)) + return true; + else if (PRIMITIVES.get(superClass.getName()) == obj.getClass()) + return true; + else + return false; + } + + public static Optional forMethod(Class cls, String name, Object... args) { + return Arrays.stream(cls.getDeclaredMethods()) + .filter(s -> name.equals(s.getName())) + .filter(s -> checkParameter(s, args)) + .findFirst(); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/SimpleMultimap.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/SimpleMultimap.java new file mode 100644 index 000000000..65e3cc6e9 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/SimpleMultimap.java @@ -0,0 +1,85 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +/** + * A simple implementation of Multimap. + * Just a combination of map and set. + * + * @author huangyuhui + */ +public final class SimpleMultimap { + + private final Map> map; + private final Supplier> valuer; + + public SimpleMultimap(Supplier>> mapper, Supplier> valuer) { + this.map = mapper.get(); + this.valuer = valuer; + } + + public int size() { + return values().size(); + } + + public Set keys() { + return map.keySet(); + } + + public Collection values() { + Collection res = valuer.get(); + for (Map.Entry> entry : map.entrySet()) + res.addAll(entry.getValue()); + return res; + } + + public boolean isEmpty() { + return size() == 0; + } + + public boolean containsKey(K key) { + return map.containsKey(key) && !map.get(key).isEmpty(); + } + + public Collection get(K key) { + return Lang.getOrPut(map, key, valuer); + } + + public void put(K key, V value) { + Collection set = get(key); + set.add(value); + } + + public Collection removeKey(K key) { + return map.remove(key); + } + + public void removeValue(V value) { + for (Collection c : map.values()) + c.remove(value); + } + + public void clear() { + map.clear(); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java new file mode 100644 index 000000000..3ae58f7e5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -0,0 +1,205 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * + * @author huangyuhui + */ +public final class StringUtils { + + private StringUtils() { + } + + public static String makeCommand(List cmd) { + StringBuilder cmdbuf = new StringBuilder(120); + for (int i = 0; i < cmd.size(); i++) { + if (i > 0) + cmdbuf.append(' '); + String s = cmd.get(i); + if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) + if (s.charAt(0) != '"') { + cmdbuf.append('"'); + cmdbuf.append(s); + if (s.endsWith("\\")) + cmdbuf.append("\\"); + cmdbuf.append('"'); + } else if (s.endsWith("\"")) + /* + * The argument has already been quoted. + */ + cmdbuf.append(s); + else + /* + * Unmatched quote for the argument. + */ + throw new IllegalArgumentException(); + else + cmdbuf.append(s); + } + String str = cmdbuf.toString(); + + return str; + } + + public static boolean isBlank(String str) { + return str == null || str.trim().isEmpty(); + } + + public static boolean isNotBlank(String str) { + return !isBlank(str); + } + + public static String substringBeforeLast(String str, char delimiter) { + return substringBeforeLast(str, delimiter, str); + } + + public static String substringBeforeLast(String str, char delimiter, String missingDelimiterValue) { + int index = str.lastIndexOf(delimiter); + return index == -1 ? missingDelimiterValue : str.substring(0, index); + } + + public static String substringBeforeLast(String str, String delimiter) { + return substringBeforeLast(str, delimiter, str); + } + + public static String substringBeforeLast(String str, String delimiter, String missingDelimiterValue) { + int index = str.lastIndexOf(delimiter); + return index == -1 ? missingDelimiterValue : str.substring(0, index); + } + + public static String substringBefore(String str, char delimiter) { + return substringBefore(str, delimiter, str); + } + + public static String substringBefore(String str, char delimiter, String missingDelimiterValue) { + int index = str.indexOf(delimiter); + return index == -1 ? missingDelimiterValue : str.substring(0, index); + } + + public static String substringBefore(String str, String delimiter) { + return substringBefore(str, delimiter, str); + } + + public static String substringBefore(String str, String delimiter, String missingDelimiterValue) { + int index = str.indexOf(delimiter); + return index == -1 ? missingDelimiterValue : str.substring(0, index); + } + + public static String substringAfterLast(String str, char delimiter) { + return substringAfterLast(str, delimiter, ""); + } + + public static String substringAfterLast(String str, char delimiter, String missingDelimiterValue) { + int index = str.lastIndexOf(delimiter); + return index == -1 ? missingDelimiterValue : str.substring(index + 1); + } + + public static String substringAfterLast(String str, String delimiter) { + return substringAfterLast(str, delimiter, ""); + } + + public static String substringAfterLast(String str, String delimiter, String missingDelimiterValue) { + int index = str.lastIndexOf(delimiter); + return index == -1 ? missingDelimiterValue : str.substring(index + delimiter.length()); + } + + public static String substringAfter(String str, char delimiter) { + return substringAfter(str, delimiter, ""); + } + + public static String substringAfter(String str, char delimiter, String missingDelimiterValue) { + int index = str.indexOf(delimiter); + return index == -1 ? missingDelimiterValue : str.substring(index + 1); + } + + public static String substringAfter(String str, String delimiter) { + return substringAfter(str, delimiter, ""); + } + + public static String substringAfter(String str, String delimiter, String missingDelimiterValue) { + int index = str.indexOf(delimiter); + return index == -1 ? missingDelimiterValue : str.substring(index + delimiter.length()); + } + + public static String removeSurrounding(String str, String delimiter) { + return removeSurrounding(str, delimiter, delimiter); + } + + public static String removeSurrounding(String str, String prefix, String suffix) { + if ((str.length() >= prefix.length() + suffix.length()) && str.startsWith(prefix) && str.endsWith(suffix)) + return str.substring(prefix.length(), str.length() - suffix.length()); + else + return str; + } + + public static String removePrefix(String str, String prefix) { + if (str.startsWith(prefix)) + return str.substring(prefix.length(), str.length()); + else + return str; + } + + public static String removeSuffix(String str, String suffix) { + if (str.endsWith(suffix)) + return str.substring(0, str.length() - suffix.length()); + else + return str; + } + + public static boolean containsOne(Collection patterns, String... targets) { + for (String pattern : patterns) + for (String target : targets) + if (pattern.toLowerCase().contains(target.toLowerCase())) + return true; + return false; + } + + public static List tokenize(String str) { + if (str == null) + return new LinkedList<>(); + else + return tokenize(str, " \t\n\r\f"); + } + + public static List tokenize(String str, String delim) { + LinkedList result = new LinkedList<>(); + StringTokenizer tokenizer = new StringTokenizer(str, delim); + while (tokenizer.hasMoreTokens()) { + delim = tokenizer.nextToken(); + result.add(delim); + } + + return result; + } + + public static Integer parseInt(String str) { + if (str == null) + return null; + try { + return Integer.parseInt(str); + } catch (NumberFormatException ex) { + return null; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringVersionNumber.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringVersionNumber.java new file mode 100644 index 000000000..942ef6f58 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringVersionNumber.java @@ -0,0 +1,70 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.Objects; + +/** + * If a version string contains alphabets, a {@code StringVersionNumber} + * will be constructed. + * + * @author huangyuhui + */ +public final class StringVersionNumber extends VersionNumber { + + private final String version; + + StringVersionNumber(String version) { + Objects.requireNonNull(version); + this.version = version; + } + + @Override + public int compareTo(VersionNumber o) { + if (!(o instanceof StringVersionNumber)) + return 0; + + return version.compareTo(((StringVersionNumber) o).version); + } + + public String getVersion() { + return version; + } + + @Override + public int hashCode() { + return version.hashCode(); + } + + @Override + public String toString() { + return version; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof VersionNumber)) + return false; + + if (!(obj instanceof StringVersionNumber)) + return true; + + return version.equals(((StringVersionNumber) obj).version); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/UUIDTypeAdapter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/UUIDTypeAdapter.java new file mode 100644 index 000000000..7973f7cfd --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/UUIDTypeAdapter.java @@ -0,0 +1,55 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.util.UUID; + +/** + * + * @author huang + */ +public final class UUIDTypeAdapter extends TypeAdapter { + + public static final UUIDTypeAdapter INSTANCE = new UUIDTypeAdapter(); + + private UUIDTypeAdapter() { + } + + @Override + public void write(JsonWriter writer, UUID value) throws IOException { + writer.value(value == null ? null : fromUUID(value)); + } + + @Override + public UUID read(JsonReader reader) throws IOException { + return fromString(reader.nextString()); + } + + public static String fromUUID(UUID value) { + return value.toString().replace("-", ""); + } + + public static UUID fromString(String input) { + return UUID.fromString(input.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Validation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Validation.java new file mode 100644 index 000000000..8dba5b3c4 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Validation.java @@ -0,0 +1,40 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import com.google.gson.JsonParseException; + +/** + * Check if the json object's fields automatically filled by Gson are in right format. + * + * @author huangyuhui + */ +public interface Validation { + + /** + * 1. Check some non-null fields and; + * 2. Check strings and; + * 3. Check generic type of lists and maps are correct. + * + * Will be called immediately after initialization. + * Throw an exception when values are malformed. + * + * @throws JsonParseException if fields are filled in wrong format or wrong type. + */ + void validate() throws JsonParseException; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ValidationTypeAdapterFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ValidationTypeAdapterFactory.java new file mode 100644 index 000000000..62121ca56 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ValidationTypeAdapterFactory.java @@ -0,0 +1,57 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +/** + * + * @author huangyuhui + */ +public final class ValidationTypeAdapterFactory implements TypeAdapterFactory { + + public static final ValidationTypeAdapterFactory INSTANCE = new ValidationTypeAdapterFactory(); + + @Override + public TypeAdapter create(Gson gson, TypeToken tt) { + final TypeAdapter delegate = gson.getDelegateAdapter(this, tt); + return new TypeAdapter() { + @Override + public void write(JsonWriter writer, T t) throws IOException { + if (t instanceof Validation) + ((Validation) t).validate(); + + delegate.write(writer, t); + } + + @Override + public T read(JsonReader reader) throws IOException { + T t = delegate.read(reader); + if (t instanceof Validation) + ((Validation) t).validate(); + return t; + } + }; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/VersionNumber.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/VersionNumber.java new file mode 100644 index 000000000..ce1d43abf --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/VersionNumber.java @@ -0,0 +1,79 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * The formatted version number represents a version string. + * + * @author huangyuhui + */ +public abstract class VersionNumber implements Comparable { + + /** + * @throws IllegalArgumentException if there are some characters excluding digits and dots. + * @param version + * @return the int version number + */ + public static IntVersionNumber asIntVersionNumber(String version) { + if (version.chars().filter(ch -> ch != '.' && (ch < '0' || ch > '9')).count() > 0 + || version.contains("..") || StringUtils.isBlank(version)) + throw new IllegalArgumentException("The version " + version + " is malformed, only dots and digits are allowed."); + + String[] s = version.split("\\."); + int last = s.length - 1; + for (int i = s.length - 1; i >= 0; --i) + if (Integer.parseInt(s[i]) == 0) + last = i; + ArrayList versions = new ArrayList<>(last + 1); + for (int i = 0; i <= last; ++i) + versions.add(Integer.parseInt(s[i])); + return new IntVersionNumber(Collections.unmodifiableList(versions)); + } + + public static StringVersionNumber asStringVersionNumber(String version) { + return new StringVersionNumber(version); + } + + public static VersionNumber asVersion(String version) { + try { + return asIntVersionNumber(version); + } catch (IllegalArgumentException e) { + return asStringVersionNumber(version); + } + } + + public static String parseVersion(String str) { + if (str.chars().anyMatch(it -> it != '.' && (it < '0' || it > '9')) || StringUtils.isBlank(str)) + return null; + String[] s = str.split("\\."); + for (String i : s) + if (StringUtils.isBlank(i)) + return null; + StringBuilder builder = new StringBuilder(); + int last = s.length - 1; + for (int i = s.length - 1; i >= 0; --i) + if (Integer.parseInt(s[i]) == 0) + last = i; + for (int i = 0; i < last; ++i) + builder.append(s[i]).append("."); + return builder.append(s[last]).toString(); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ZipEngine.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ZipEngine.java new file mode 100644 index 000000000..93b6003f7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ZipEngine.java @@ -0,0 +1,132 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + + +import java.io.*; +import java.util.HashSet; +import java.util.function.BiFunction; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * Non thread-safe + * + * @author huangyuhui + */ +public class ZipEngine implements Closeable { + + byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; + ZipOutputStream zos; + + public ZipEngine(File f) throws IOException { + zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f))); + } + + @Override + public void close() throws IOException { + zos.closeEntry(); + zos.close(); + } + + public void putDirectory(File sourceDir) throws IOException { + putDirectory(sourceDir, null); + } + + /** + * 功能:把 sourceDir 目录下的所有文件进行 zip 格式的压缩,保存为指定 zip 文件 + * + * @param sourceDir 源文件夹 + * @param pathNameCallback callback(pathName, isDirectory) returns your + * modified pathName + * + * @throws java.io.IOException 压缩失败或无法读取 + */ + public void putDirectory(File sourceDir, BiFunction pathNameCallback) throws IOException { + putDirectoryImpl(sourceDir, sourceDir.isDirectory() ? sourceDir.getPath() : sourceDir.getParent(), pathNameCallback); + } + + /** + * 将文件压缩成zip文件 + * + * @param source zip文件路径 + * @param basePath 待压缩文件根目录 + * @param pathNameCallback callback(pathName, isDirectory) returns your + * modified pathName, null if you dont want this file zipped + */ + private void putDirectoryImpl(File source, String basePath, BiFunction pathNameCallback) throws IOException { + File[] files; + if (source.isDirectory()) + files = source.listFiles(); + else + files = new File[] { source }; + + if (files == null) + return; + String pathName;//存相对路径(相对于待压缩的根目录) + for (File file : files) + if (file.isDirectory()) { + pathName = file.getPath().substring(basePath.length() + 1) + + "/"; + pathName = pathName.replace('\\', '/'); + if (pathNameCallback != null) + pathName = pathNameCallback.apply(pathName, true); + if (pathName == null) + continue; + put(new ZipEntry(pathName)); + putDirectoryImpl(file, basePath, pathNameCallback); + } else { + if (".DS_Store".equals(file.getName())) // For Mac computers. + continue; + pathName = file.getPath().substring(basePath.length() + 1); + pathName = pathName.replace('\\', '/'); + if (pathNameCallback != null) + pathName = pathNameCallback.apply(pathName, false); + if (pathName == null) + continue; + putFile(file, pathName); + } + } + + public void putFile(File file, String pathName) throws IOException { + try (FileInputStream fis = new FileInputStream(file)) { + putStream(fis, pathName); + } + } + + public void putStream(InputStream is, String pathName) throws IOException { + put(new ZipEntry(pathName)); + IOUtils.copyTo(is, zos, buf); + } + + public void putTextFile(String text, String pathName) throws IOException { + putTextFile(text, "UTF-8", pathName); + } + + public void putTextFile(String text, String encoding, String pathName) throws IOException { + putStream(new ByteArrayInputStream(text.getBytes(encoding)), pathName); + } + + protected HashSet names = new HashSet<>(); + + public void put(ZipEntry entry) throws IOException { + if (names.add(entry.getName())) + zos.putNextEntry(entry); + } + +} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/Account.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/Account.kt deleted file mode 100644 index 2f76287eb..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/Account.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth - -import java.net.Proxy - -abstract class Account() { - abstract val username: String - @Throws(AuthenticationException::class) - abstract fun logIn(proxy: Proxy = Proxy.NO_PROXY): AuthInfo - abstract fun logOut() - abstract fun toStorage(): MutableMap -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AccountFactory.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AccountFactory.kt deleted file mode 100644 index c2ab52f9c..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AccountFactory.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth - -interface AccountFactory { - fun fromUsername(username: String, password: String = ""): T - fun fromStorage(storage: Map): T -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AuthInfo.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AuthInfo.kt deleted file mode 100644 index f2d9b564f..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AuthInfo.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth - -import org.jackhuang.hmcl.auth.yggdrasil.GameProfile -import org.jackhuang.hmcl.util.Immutable -import org.jackhuang.hmcl.util.UUIDTypeAdapter - -@Immutable -data class AuthInfo @JvmOverloads constructor( - val username: String, - val userId: String, - val authToken: String, - val userType: UserType = UserType.LEGACY, - val userProperties: String = "{}", - val userPropertyMap: String = "{}" -) { - constructor(profile: GameProfile, - authToken: String, - userType: UserType = UserType.LEGACY, - userProperties: String = "{}", - userPropertyMap: String = "{}") - : this(profile.name!!, UUIDTypeAdapter.fromUUID(profile.id!!), authToken, userType, userProperties, userPropertyMap) { - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AuthenticationException.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AuthenticationException.kt deleted file mode 100644 index 1faede083..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/AuthenticationException.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth - -open class AuthenticationException : Exception { - constructor() : super() {} - constructor(message: String) : super(message) {} - constructor(message: String, cause: Throwable) : super(message, cause) {} -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/OfflineAccount.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/OfflineAccount.kt deleted file mode 100644 index 59c76a845..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/OfflineAccount.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth - -import org.jackhuang.hmcl.util.DigestUtils -import java.net.Proxy - -class OfflineAccount private constructor(val uuid: String, override val username: String): Account() { - - init { - if (username.isBlank()) - throw IllegalArgumentException("Username cannot be blank") - } - - override fun logIn(proxy: Proxy): AuthInfo { - if (username.isBlank() || uuid.isBlank()) - throw AuthenticationException("Username cannot be empty") - return AuthInfo( - username = username, - userId = uuid, - authToken = uuid - ) - } - - override fun logOut() { - // Offline account need not log out. - } - - override fun toStorage(): MutableMap { - return mutableMapOf( - "uuid" to uuid, - "username" to username - ) - } - - override fun toString() = "OfflineAccount[username=$username,uuid=$uuid]" - - companion object OfflineAccountFactory : AccountFactory { - - override fun fromUsername(username: String, password: String): OfflineAccount { - return OfflineAccount( - username = username, - uuid = getUUIDFromUserName(username) - ) - } - - override fun fromStorage(storage: Map): OfflineAccount { - val username = storage["username"] as? String - ?: throw IllegalStateException("Configuration is malformed.") - val obj = storage["uuid"] - return OfflineAccount( - username = username, - uuid = obj as? String ?: getUUIDFromUserName(username) - ) - } - - private fun getUUIDFromUserName(username: String) = DigestUtils.md5Hex(username) - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.kt deleted file mode 100644 index 4aa57bcc4..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil - -data class AuthenticationRequest @JvmOverloads constructor( - val username: String, - val password: String, - val clientToken: String, - val agent: Map = mapOf( - "name" to "Minecraft", - "version" to 1 - ), - val requestUser: Boolean = true -) { -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Exceptions.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Exceptions.kt deleted file mode 100644 index 34ab880a7..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Exceptions.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil - -import org.jackhuang.hmcl.auth.AuthenticationException - -class InvalidTokenException(val account: YggdrasilAccount) : AuthenticationException() - -class InvalidCredentialsException(val account: YggdrasilAccount) : AuthenticationException() \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.kt deleted file mode 100644 index 69fa1a501..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/GameProfile.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil - -import com.google.gson.* -import java.lang.reflect.Type -import java.util.* - -data class GameProfile @JvmOverloads constructor( - val id: UUID? = null, - val name: String? = null, - val properties: PropertyMap = PropertyMap(), - val legacy: Boolean = false -) { - companion object GameProfileSerializer: JsonSerializer, JsonDeserializer { - override fun serialize(src: GameProfile, typeOfSrc: Type?, context: JsonSerializationContext): JsonElement { - val result = JsonObject() - if (src.id != null) - result.add("id", context.serialize(src.id)) - if (src.name != null) - result.addProperty("name", src.name) - return result - } - - override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext): GameProfile { - if (json !is JsonObject) - throw JsonParseException("The json element is not a JsonObject.") - val id = if (json.has("id")) context.deserialize(json.get("id"), UUID::class.java) else null - val name = if (json.has("name")) json.getAsJsonPrimitive("name").asString else null - return GameProfile(id, name) - } - - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/PropertyMap.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/PropertyMap.kt deleted file mode 100644 index 071af44e9..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/PropertyMap.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil - -import com.google.gson.* -import java.lang.reflect.Type -import java.util.* - - -class PropertyMap: HashMap() { - - fun toList(): List> { - val properties = LinkedList>() - values.forEach { (name, value) -> - properties += mapOf( - "name" to name, - "value" to value - ) - } - return properties - } - - /** - * Load property map from list. - * @param list Right type is List>. Using List<*> here because of fault tolerance - */ - fun fromList(list: List<*>) { - list.forEach { propertyMap -> - if (propertyMap is Map<*, *>) { - val name = propertyMap["name"] as? String - val value = propertyMap["value"] as? String - if (name != null && value != null) - put(name, Property(name, value)) - } - } - } - - companion object Serializer : JsonSerializer, JsonDeserializer { - override fun serialize(src: PropertyMap, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { - val result = JsonArray() - for ((name, value) in src.values) - result.add(JsonObject().apply { - addProperty("name", name) - addProperty("value", value) - }) - - return result - } - - override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): PropertyMap { - val result = PropertyMap() - if (json is JsonObject) { - for ((key, value) in json.entrySet()) - if (value is JsonArray) - for (element in value) - result.put(key, Property(key, element.asString)) - } else if (json is JsonArray) - for (element in json) - if (element is JsonObject) { - val name = element.getAsJsonPrimitive("name").asString - val value = element.getAsJsonPrimitive("value").asString - result.put(name, Property(name, value)) - } - return result - } - - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/RefreshRequest.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/RefreshRequest.kt deleted file mode 100644 index 024227450..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/RefreshRequest.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil - -data class RefreshRequest @JvmOverloads constructor( - val clientToken: String, - val accessToken: String, - val selectedProfile: GameProfile? = null, - val requestUser: Boolean = true -) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Response.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Response.kt deleted file mode 100644 index 5127a8a46..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/Response.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil - -class Response @JvmOverloads constructor( - val accessToken: String? = null, - val clientToken: String? = null, - val selectedProfile: GameProfile? = null, - val availableProfiles: Array? = null, - val user: User? = null, - val error: String? = null, - val errorMessage: String? = null, - val cause: String? = null -) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/User.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/User.kt deleted file mode 100644 index b2b8225fb..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/User.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil - -import com.google.gson.JsonParseException -import org.jackhuang.hmcl.util.Validation - -class User @JvmOverloads constructor( - val id: String = "", - val properties: PropertyMap? = null -) : Validation { - override fun validate() { - if (id.isBlank()) - throw JsonParseException("User id cannot be empty") - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/ValidateRequest.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/ValidateRequest.kt deleted file mode 100644 index 32cba9bf5..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/ValidateRequest.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil - -data class ValidateRequest @JvmOverloads constructor( - val clientToken: String = "", - val accessToken: String = "" -) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.kt deleted file mode 100644 index 74d9be683..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.kt +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.auth.yggdrasil - -import com.google.gson.GsonBuilder -import com.google.gson.JsonParseException -import org.jackhuang.hmcl.auth.* -import org.jackhuang.hmcl.util.* -import java.io.IOException -import java.net.Proxy -import java.net.URL -import java.util.* - -class YggdrasilAccount private constructor(override val username: String): Account() { - private var password: String? = null - private var userId: String? = null - private var accessToken: String? = null - private var clientToken: String = randomToken() - private var isOnline: Boolean = false - private var userProperties = PropertyMap() - var selectedProfile: GameProfile? = null - private set - private var profiles: Array? = null - private var userType: UserType = UserType.LEGACY - - init { - if (username.isBlank()) - throw IllegalArgumentException("Username cannot be blank") - if (!username.contains("@")) - throw IllegalArgumentException("Yggdrasil account user name must be email") - } - - val isLoggedIn: Boolean - get() = isNotBlank(accessToken) - - val canPlayOnline: Boolean - get() = isLoggedIn && selectedProfile != null && isOnline - - val canLogIn: Boolean - get() = !canPlayOnline && username.isNotBlank() && (isNotBlank(password) || isNotBlank((accessToken))) - - override fun logIn(proxy: Proxy): AuthInfo { - if (canPlayOnline) - return AuthInfo( - profile = selectedProfile!!, - authToken = accessToken!!, - userType = userType, - userProperties = GSON.toJson(userProperties) - ) - else { - logIn0(proxy) - if (!isLoggedIn) - throw AuthenticationException("Wrong password for account $username") - if (selectedProfile == null) { - // TODO: multi-available-profiles support - throw UnsupportedOperationException("Do not support multi-available-profiles account yet.") - } else { - return AuthInfo( - profile = selectedProfile!!, - authToken = accessToken!!, - userType = userType, - userProperties = GSON.toJson(userProperties) - ) - } - } - } - - private fun logIn0(proxy: Proxy) { - if (isNotBlank(accessToken)) { - if (isBlank(userId)) - if (isNotBlank(username)) - userId = username - else - throw AuthenticationException("Invalid uuid and username") - if (checkTokenValidity(proxy)) { - isOnline = true - return - } - logIn1(ROUTE_REFRESH, RefreshRequest(clientToken = clientToken, accessToken = accessToken!!), proxy) - } else if (isNotBlank(password)) { - logIn1(ROUTE_AUTHENTICATE, AuthenticationRequest(username, password!!, clientToken), proxy) - } else - throw AuthenticationException("Password cannot be blank") - } - - private fun logIn1(url: URL, input: Any, proxy: Proxy) { - val response = makeRequest(url, input, proxy) - - if (clientToken != response?.clientToken) - throw AuthenticationException("Client token changed") - - if (response.selectedProfile != null) - userType = UserType.fromLegacy(response.selectedProfile.legacy) - else if (response.availableProfiles?.getOrNull(0) != null) - userType = UserType.fromLegacy(response.availableProfiles[0].legacy) - - val user = response.user - userId = user?.id ?: username - isOnline = true - profiles = response.availableProfiles - selectedProfile = response.selectedProfile - userProperties.clear() - accessToken = response.accessToken - - if (user?.properties != null) - userProperties.putAll(user.properties) - } - - override fun logOut() { - password = null - userId = null - accessToken = null - isOnline = false - userProperties.clear() - profiles = null - selectedProfile = null - } - - override fun toStorage(): MutableMap { - val result = HashMap() - - result[STORAGE_KEY_USER_NAME] = username - result[STORAGE_KEY_CLIENT_TOKEN] = clientToken - if (userId != null) - result[STORAGE_KEY_USER_ID] = userId!! - if (!userProperties.isEmpty()) - result[STORAGE_KEY_USER_PROPERTIES] = userProperties.toList() - val profile = selectedProfile - if (profile?.name != null && profile.id != null) { - result[STORAGE_KEY_PROFILE_NAME] = profile.name - result[STORAGE_KEY_PROFILE_ID] = UUIDTypeAdapter.fromUUID(profile.id) - if (!profile.properties.isEmpty()) - result[STORAGE_KEY_PROFILE_PROPERTIES] = profile.properties.toList() - } - if (!accessToken.isNullOrBlank()) - result[STORAGE_KEY_ACCESS_TOKEN] = accessToken!! - - return result - } - - private fun makeRequest(url: URL, input: Any?, proxy: Proxy): Response? { - try { - val jsonResult = - if (input == null) url.doGet(proxy) - else url.doPost(GSON.toJson(input), "application/json", proxy) - - val response = GSON.fromJson(jsonResult) ?: return null - - if (!response.error.isNullOrBlank()) { - LOG.severe("Failed to log in, the auth server returned an error: " + response.error + ", message: " + response.errorMessage + ", cause: " + response.cause) - if (response.errorMessage != null) - if (response.errorMessage.contains("Invalid credentials")) - throw InvalidCredentialsException(this) - else if (response.errorMessage.contains("Invalid token")) - throw InvalidTokenException(this) - throw AuthenticationException("Request error: ${response.errorMessage}") - } - - return response - } catch (e: IOException) { - throw AuthenticationException("Unable to connect to authentication server", e) - } catch (e: JsonParseException) { - throw AuthenticationException("Unable to parse server response", e) - } - } - - private fun checkTokenValidity(proxy: Proxy): Boolean { - val access = accessToken ?: return false - try { - makeRequest(ROUTE_VALIDATE, ValidateRequest(clientToken, access), proxy) - return true - } catch (e: AuthenticationException) { - return false - } - } - - override fun toString() = "YggdrasilAccount[username=$username]" - - companion object YggdrasilAccountFactory : AccountFactory { - private val GSON = GsonBuilder() - .registerTypeAdapter(GameProfile::class.java, GameProfile) - .registerTypeAdapter(PropertyMap::class.java, PropertyMap) - .registerTypeAdapter(UUID::class.java, UUIDTypeAdapter) - .create() - - private val BASE_URL = "https://authserver.mojang.com/" - private val ROUTE_AUTHENTICATE = (BASE_URL + "authenticate").toURL() - private val ROUTE_REFRESH = (BASE_URL + "refresh").toURL() - private val ROUTE_VALIDATE = (BASE_URL + "validate").toURL() - - private val STORAGE_KEY_ACCESS_TOKEN = "accessToken" - private val STORAGE_KEY_PROFILE_NAME = "displayName" - private val STORAGE_KEY_PROFILE_ID = "uuid" - private val STORAGE_KEY_PROFILE_PROPERTIES = "profileProperties" - private val STORAGE_KEY_USER_NAME = "username" - private val STORAGE_KEY_USER_ID = "userid" - private val STORAGE_KEY_USER_PROPERTIES = "userProperties" - private val STORAGE_KEY_CLIENT_TOKEN = "clientToken" - - fun randomToken() = UUIDTypeAdapter.fromUUID(UUID.randomUUID()) - - override fun fromUsername(username: String, password: String) = YggdrasilAccount(username).apply { - this.password = password - } - - override fun fromStorage(storage: Map): YggdrasilAccount { - val username = storage[STORAGE_KEY_USER_NAME] as? String? ?: throw IllegalArgumentException("storage does not have key $STORAGE_KEY_USER_NAME") - val account = YggdrasilAccount(username) - account.userId = storage[STORAGE_KEY_USER_ID] as? String ?: username - account.accessToken = storage[STORAGE_KEY_ACCESS_TOKEN] as? String - account.clientToken = storage[STORAGE_KEY_CLIENT_TOKEN] as? String? ?: throw IllegalArgumentException("storage does not have key $STORAGE_KEY_CLIENT_TOKEN") - val userProperties = storage[STORAGE_KEY_USER_PROPERTIES] as? List<*> - if (userProperties != null) - account.userProperties.fromList(userProperties) - val profileId = storage[STORAGE_KEY_PROFILE_ID] as? String - val profileName = storage[STORAGE_KEY_PROFILE_NAME] as? String - val profile: GameProfile? - if (profileId != null && profileName != null) { - profile = GameProfile(UUIDTypeAdapter.fromString(profileId), profileName) - val profileProperties = storage[STORAGE_KEY_PROFILE_PROPERTIES] as? List<*> - if (profileProperties != null) - profile.properties.fromList(profileProperties) - } else - profile = null - account.selectedProfile = profile - - return account - } - - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.kt deleted file mode 100644 index 9f5eadbd4..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download - -import org.jackhuang.hmcl.download.forge.ForgeVersionList -import org.jackhuang.hmcl.download.game.GameVersionList -import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList -import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList - -/** - * @see {@link http://bmclapi2.bangbang93.com} - */ -object BMCLAPIDownloadProvider : DownloadProvider { - override val libraryBaseURL: String = "http://bmclapi2.bangbang93.com/libraries/" - override val versionListURL: String = "http://bmclapi2.bangbang93.com/mc/game/version_manifest.json" - override val versionBaseURL: String = "http://bmclapi2.bangbang93.com/versions/" - override val assetIndexBaseURL: String = "http://bmclapi2.bangbang93.com/indexes/" - override val assetBaseURL: String = "http://bmclapi2.bangbang93.com/assets/" - - override fun getVersionListById(id: String): VersionList<*> { - return when(id) { - "game" -> GameVersionList - "forge" -> ForgeVersionList - "liteloader" -> LiteLoaderVersionList - "optifine" -> OptiFineBMCLVersionList - else -> throw IllegalArgumentException("Unrecognized version list id: $id") - } - } - - override fun injectURL(baseURL: String): String = baseURL - .replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com") - .replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com") - .replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries") - .replace("http://files.minecraftforge.net/maven", "https://bmclapi2.bangbang93.com/maven") - .replace("http://dl.liteloader.com/versions/versions.json", "httsp://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json") - .replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven") -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultDependencyManager.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultDependencyManager.kt deleted file mode 100644 index c241de889..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultDependencyManager.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download - -import org.jackhuang.hmcl.download.forge.ForgeInstallTask -import org.jackhuang.hmcl.download.game.GameAssetDownloadTask -import org.jackhuang.hmcl.download.game.GameLibrariesTask -import org.jackhuang.hmcl.download.game.GameLoggingDownloadTask -import org.jackhuang.hmcl.download.game.VersionJSONSaveTask -import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask -import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask -import org.jackhuang.hmcl.game.DefaultGameRepository -import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.task.ParallelTask -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.then -import java.net.Proxy - -/** - * This class has no state. - */ -class DefaultDependencyManager @JvmOverloads constructor(override val repository: DefaultGameRepository, override var downloadProvider: DownloadProvider, override val proxy: Proxy = Proxy.NO_PROXY) - : AbstractDependencyManager() { - - override fun gameBuilder(): GameBuilder = DefaultGameBuilder(this) - - override fun checkGameCompletionAsync(version: Version): Task { - val tasks: Array = arrayOf( - GameAssetDownloadTask(this, version), - GameLoggingDownloadTask(this, version), - GameLibrariesTask(this, version) - ) - return ParallelTask(*tasks) - } - - override fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task = - when (libraryId) { - "forge" -> - ForgeInstallTask(this, gameVersion, version, libraryVersion) - .then { VersionJSONSaveTask(repository, it["version"]) } - "liteloader" -> - LiteLoaderInstallTask(this, gameVersion, version, libraryVersion) - .then { VersionJSONSaveTask(repository, it["version"]) } - "optifine" -> - OptiFineInstallTask(this, gameVersion, version, libraryVersion) - .then { VersionJSONSaveTask(repository, it["version"]) } - else -> - throw IllegalArgumentException("Library id $libraryId is unrecognized.") - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultGameBuilder.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultGameBuilder.kt deleted file mode 100644 index 6f59c3d8c..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultGameBuilder.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download - -import org.jackhuang.hmcl.download.game.GameAssetDownloadTask -import org.jackhuang.hmcl.download.game.GameLibrariesTask -import org.jackhuang.hmcl.download.game.GameLoggingDownloadTask -import org.jackhuang.hmcl.download.game.VersionJSONSaveTask -import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.task.* -import org.jackhuang.hmcl.util.AutoTypingMap -import org.jackhuang.hmcl.util.GSON -import org.jackhuang.hmcl.util.fromJson -import org.jackhuang.hmcl.util.toURL -import java.util.* - -class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameBuilder() { - val repository = dependencyManager.repository - val downloadProvider = dependencyManager.downloadProvider - - override fun buildAsync(): Task { - val gameVersion = gameVersion - return VersionJSONDownloadTask(gameVersion, dependencyManager, "raw_version_json") - .then { - var version = GSON.fromJson(it["raw_version_json"])!! - it["version"] = version - version = version.copy(id = name, jar = null) - var result = ParallelTask( - GameAssetDownloadTask(dependencyManager, version), - GameLoggingDownloadTask(dependencyManager, version), - GameDownloadTask(version), - GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries. - ) with VersionJSONSaveTask(dependencyManager.repository, version) // using [with] because download failure here are tolerant. - - if (toolVersions.containsKey("forge")) - result = result then libraryTaskHelper(gameVersion, "forge") - if (toolVersions.containsKey("liteloader")) - result = result then libraryTaskHelper(gameVersion, "liteloader") - if (toolVersions.containsKey("optifine")) - result = result then libraryTaskHelper(gameVersion, "optifine") - result - } - } - - private fun libraryTaskHelper(gameVersion: String, libraryId: String): (AutoTypingMap) -> Task = { - dependencyManager.installLibraryAsync(gameVersion, it["version"], libraryId, toolVersions[libraryId]!!) - } - - private class VersionJSONDownloadTask(val gameVersion: String, val dependencyManager: DefaultDependencyManager, val id: String): Task() { - override val dependents: MutableCollection = LinkedList() - override val dependencies: MutableCollection = LinkedList() - - private val gameVersionList: VersionList<*> = dependencyManager.getVersionList("game") - init { - if (!gameVersionList.loaded) - dependents += gameVersionList.refreshAsync(dependencyManager.downloadProvider) - } - - override fun execute() { - val remoteVersion = gameVersionList.getVersions(gameVersion).firstOrNull() - ?: throw Error("Cannot find specific version $gameVersion in remote repository") - - val jsonURL = dependencyManager.downloadProvider.injectURL(remoteVersion.url) - dependencies += GetTask(jsonURL.toURL(), proxy = dependencyManager.proxy, id = id) - } - } - - inner class GameDownloadTask(var version: Version) : Task() { - override val dependencies: MutableCollection = LinkedList() - override fun execute() { - val jar = repository.getVersionJar(version) - - dependencies += FileDownloadTask( - url = downloadProvider.injectURL(version.download.url).toURL(), - file = jar, - hash = version.download.sha1, - proxy = dependencyManager.proxy) - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/MojangDownloadProvider.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/MojangDownloadProvider.kt deleted file mode 100644 index 49851b8ee..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/MojangDownloadProvider.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download - -import org.jackhuang.hmcl.download.forge.ForgeVersionList -import org.jackhuang.hmcl.download.game.GameVersionList -import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList -import org.jackhuang.hmcl.download.optifine.OptiFineVersionList -import java.util.* - -/** - * @see {@link http://wiki.vg} - */ -object MojangDownloadProvider : DownloadProvider { - override val libraryBaseURL: String = "https://libraries.minecraft.net/" - override val versionListURL: String = "https://launchermeta.mojang.com/mc/game/version_manifest.json" - override val versionBaseURL: String = "http://s3.amazonaws.com/Minecraft.Download/versions/" - override val assetIndexBaseURL: String = "http://s3.amazonaws.com/Minecraft.Download/indexes/" - override val assetBaseURL: String = "http://resources.download.minecraft.net/" - - override fun getVersionListById(id: String): VersionList<*> { - return when(id) { - "game" -> GameVersionList - "forge" -> ForgeVersionList - "liteloader" -> LiteLoaderVersionList - "optifine" -> OptiFineVersionList - else -> throw IllegalArgumentException("Unrecognized version list id: $id") - } - } - - override fun injectURL(baseURL: String): String { - if (baseURL.contains("net/minecraftforge/forge")) - return baseURL - else - return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven") - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/RemoteVersion.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/RemoteVersion.kt deleted file mode 100644 index b1f760058..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/RemoteVersion.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download - -import org.jackhuang.hmcl.util.VersionNumber -import java.util.* - -/** - * The remote version. - * - * @param gameVersion the Minecraft version that this remote version suits. - * @param selfVersion the version string of the remote version. - * @param url the installer or universal jar URL. - * @param tag some necessary information for Installer Task. - */ -data class RemoteVersion ( - val gameVersion: String, - val selfVersion: String, - val url: String, - val tag: T -): Comparable> { - override fun hashCode(): Int { - return selfVersion.hashCode() - } - - override fun equals(other: Any?): Boolean { - return other is RemoteVersion<*> && Objects.equals(this.selfVersion, other.selfVersion) - } - - override fun compareTo(other: RemoteVersion): Int { - // newer versions are smaller than older versions - return -selfVersion.compareTo(other.selfVersion) - } - - companion object RemoteVersionComparator: Comparator> { - override fun compare(o1: RemoteVersion<*>, o2: RemoteVersion<*>): Int { - return -VersionNumber.asVersion(o1.selfVersion).compareTo(VersionNumber.asVersion(o2.selfVersion)) - } - } -} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeInstallTask.kt deleted file mode 100644 index 07b923954..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeInstallTask.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.forge - -import org.apache.commons.compress.archivers.zip.ZipFile -import org.jackhuang.hmcl.download.DefaultDependencyManager -import org.jackhuang.hmcl.download.RemoteVersion -import org.jackhuang.hmcl.download.game.GameLibrariesTask -import org.jackhuang.hmcl.game.Library -import org.jackhuang.hmcl.game.SimpleVersionProvider -import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.task.FileDownloadTask -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.TaskResult -import org.jackhuang.hmcl.task.then -import org.jackhuang.hmcl.util.* -import java.io.File -import java.io.IOException - -class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager, - private val gameVersion: String, - private val version: Version, - private val remoteVersion: String) : TaskResult() { - private val forgeVersionList = dependencyManager.getVersionList("forge") - private val installer: File = File("forge-installer.jar").absoluteFile - lateinit var remote: RemoteVersion<*> - override val dependents = mutableListOf() - override val dependencies = mutableListOf() - override val id = "version" - - override val reliesOnDependencies = false - - init { - if (!forgeVersionList.loaded) - dependents += forgeVersionList.refreshAsync(dependencyManager.downloadProvider).then { - remote = forgeVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote forge version $gameVersion, $remoteVersion not found") - FileDownloadTask(remote.url.toURL(), installer) - } - else { - remote = forgeVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote forge version $gameVersion, $remoteVersion not found") - dependents += FileDownloadTask(remote.url.toURL(), installer) - } - } - - override fun execute() { - ZipFile(installer).use { zipFile -> - val installProfile = GSON.fromJson(zipFile.getInputStream(zipFile.getEntry("install_profile.json")).readFullyAsString()) ?: throw IOException("install_profile.json is not found.") - - // unpack the universal jar in the installer file. - - val forgeLibrary = Library.fromName(installProfile.install!!.path!!) - val forgeFile = dependencyManager.repository.getLibraryFile(version, forgeLibrary) - if (!forgeFile.makeFile()) - throw IOException("Cannot make directory ${forgeFile.parentFile}") - - val forgeEntry = zipFile.getEntry(installProfile.install.filePath) - zipFile.getInputStream(forgeEntry).copyToAndClose(forgeFile.outputStream()) - - // resolve the version - val versionProvider = SimpleVersionProvider() - versionProvider.addVersion(version) - - result = installProfile.versionInfo!!.copy(inheritsFrom = version.id).resolve(versionProvider).copy(id = version.id, logging = emptyMap()) - dependencies += GameLibrariesTask(dependencyManager, installProfile.versionInfo) - } - - check(installer.delete(), { "Unable to delete installer file $installer" }) - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeRemote.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeRemote.kt deleted file mode 100644 index e8cd691db..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeRemote.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.forge - -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.util.Immutable -import org.jackhuang.hmcl.util.Validation - -@Immutable -internal class ForgeVersion @JvmOverloads constructor( - val branch: String? = null, - val mcversion: String? = null, - val jobver: String? = null, - val version: String? = null, - val build: Int = 0, - val modified: Double = 0.0, - val files: Array>? = null -) : Validation { - override fun validate() { - check(files != null, { "ForgeVersion files cannot be null" }) - check(version != null, { "ForgeVersion version cannot be null" }) - check(mcversion != null, { "ForgeVersion mcversion cannot be null" }) - } -} - -@Immutable -internal class ForgeVersionRoot @JvmOverloads constructor( - val artifact: String? = null, - val webpath: String? = null, - val adfly: String? = null, - val homepage: String? = null, - val name: String? = null, - val branches: Map>? = null, - val mcversion: Map>? = null, - val promos: Map? = null, - val number: Map? = null -) : Validation { - override fun validate() { - check(number != null, { "ForgeVersionRoot number cannot be null" }) - check(mcversion != null, { "ForgeVersionRoot mcversion cannot be null" }) - } -} - -@Immutable -internal data class Install @JvmOverloads constructor( - val profileName: String? = null, - val target: String? = null, - val path: String? = null, - val version: String? = null, - val filePath: String? = null, - val welcome: String? = null, - val minecraft: String? = null, - val mirrorList: String? = null, - val logo: String? = null -) - -@Immutable -internal data class InstallProfile @JvmOverloads constructor( - @SerializedName("install") - val install: Install? = null, - @SerializedName("versionInfo") - val versionInfo: Version? = null -) : Validation { - override fun validate() { - check(install != null, { "InstallProfile install cannot be null" }) - check(versionInfo != null, { "InstallProfile versionInfo cannot be null" }) - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeVersionList.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeVersionList.kt deleted file mode 100644 index 003e75221..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeVersionList.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.forge - -import org.jackhuang.hmcl.download.DownloadProvider -import org.jackhuang.hmcl.download.RemoteVersion -import org.jackhuang.hmcl.download.VersionList -import org.jackhuang.hmcl.task.GetTask -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.util.* - -object ForgeVersionList : VersionList() { - @JvmField - val FORGE_LIST = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/json" - - override fun refreshAsync(downloadProvider: DownloadProvider): Task { - return RefreshTask(downloadProvider) - } - - private class RefreshTask(val downloadProvider: DownloadProvider): Task() { - val task = GetTask(downloadProvider.injectURL(FORGE_LIST).toURL()) - override val dependents: Collection = listOf(task) - override fun execute() { - val root = GSON.fromJson(task.result!!) ?: return - versions.clear() - - for ((x, versions) in root.mcversion!!.entries) { - val gameVersion = x.asVersion() ?: continue - for (v in versions) { - val version = root.number!![v] ?: continue - var jar: String? = null - for (file in version.files!!) - if (file.getOrNull(1) == "installer") { - val classifier = "${version.mcversion}-${version.version}" + ( - if (isNotBlank(version.branch)) - "-${version.branch}" - else - "" - ) - val fileName = "${root.artifact}-$classifier-${file[1]}.${file[0]}" - jar = downloadProvider.injectURL("${root.webpath}$classifier/$fileName") - } - - if (jar == null) continue - val remoteVersion = RemoteVersion( - gameVersion = version.mcversion!!, - selfVersion = version.version!!, - url = jar, - tag = Unit - ) - ForgeVersionList.versions.put(gameVersion, remoteVersion) - } - } - } - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameDownloadTasks.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameDownloadTasks.kt deleted file mode 100644 index 112e4bdeb..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameDownloadTasks.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.game - -import org.jackhuang.hmcl.download.AbstractDependencyManager -import org.jackhuang.hmcl.download.DefaultDependencyManager -import org.jackhuang.hmcl.download.DependencyManager -import org.jackhuang.hmcl.game.* -import org.jackhuang.hmcl.task.FileDownloadTask -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.TaskResult -import org.jackhuang.hmcl.util.* -import java.io.File -import java.io.IOException -import java.util.* -import java.util.logging.Level - -/** - * This task is to download game libraries. - * This task should be executed last(especially after game downloading, Forge, LiteLoader and OptiFine install task) - * @param resolvedVersion the resolved version - */ -class GameLibrariesTask(private val dependencyManager: AbstractDependencyManager, private val resolvedVersion: Version): Task() { - override val dependencies = LinkedList() - override fun execute() { - for (library in resolvedVersion.libraries) - if (library.appliesToCurrentEnvironment) { - val file = dependencyManager.repository.getLibraryFile(resolvedVersion, library) - if (!file.exists()) - dependencies += FileDownloadTask(dependencyManager.downloadProvider.injectURL(library.download.url).toURL(), file, library.download.sha1, proxy = dependencyManager.proxy) - } - } - -} - -/** - * This task is to download log4j configuration file provided in minecraft.json. - * @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository] - * @param version the **resolved** version - */ -class GameLoggingDownloadTask(private val dependencyManager: DependencyManager, private val version: Version) : Task() { - override val dependencies = LinkedList() - override fun execute() { - val logging = version.logging?.get(DownloadType.CLIENT) ?: return - val file = dependencyManager.repository.getLoggingObject(version.id, version.actualAssetIndex.id, logging) - if (!file.exists()) - dependencies += FileDownloadTask(logging.file.url.toURL(), file, proxy = dependencyManager.proxy) - } -} - -/** - * This task is to download asset index file provided in minecraft.json. - * @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository] - * @param version the **resolved** version - */ -class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { - override val dependencies = LinkedList() - override fun execute() { - val assetIndexInfo = version.actualAssetIndex - val assetDir = dependencyManager.repository.getAssetDirectory(version.id, assetIndexInfo.id) - if (!assetDir.makeDirectory()) - throw IOException("Cannot create directory: $assetDir") - - val assetIndexFile = dependencyManager.repository.getIndexFile(version.id, assetIndexInfo.id) - dependencies += FileDownloadTask(dependencyManager.downloadProvider.injectURL(assetIndexInfo.url).toURL(), assetIndexFile, proxy = dependencyManager.proxy) - } -} - -/** - * This task is to extract all asset objects described in asset index json. - * @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository] - * @param version the **resolved** version - */ -class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : TaskResult>>() { - private val assetIndexTask = GameAssetIndexDownloadTask(dependencyManager, version) - private val assetIndexInfo = version.actualAssetIndex - private val assetIndexFile = dependencyManager.repository.getIndexFile(version.id, assetIndexInfo.id) - override val dependents = LinkedList() - override val id = ID - - init { - if (!assetIndexFile.exists()) - dependents += assetIndexTask - } - - override fun execute() { - val index = GSON.fromJson(assetIndexFile.readText()) - val res = LinkedList>() - var progress = 0 - index?.objects?.values?.forEach { assetObject -> - res += Pair(dependencyManager.repository.getAssetObject(version.id, assetIndexInfo.id, assetObject), assetObject) - updateProgress(++progress, index.objects.size) - } - result = res - } - - companion object { - val ID = "game_asset_refresh_task" - } -} - -/** - * This task is to download all asset objects provided in asset index json. - * @param dependencyManager the dependency manager that can provides proxy settings and [GameRepository] - * @param version the **resolved** version - */ -class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, version: Version) : Task() { - private val refreshTask = GameAssetRefreshTask(dependencyManager, version) - override val dependents = listOf(refreshTask) - override val dependencies = LinkedList() - override fun execute() { - val size = refreshTask.result?.size ?: 0 - refreshTask.result?.forEach single@{ (file, assetObject) -> - val url = dependencyManager.downloadProvider.assetBaseURL + assetObject.location - if (!file.absoluteFile.parentFile.makeDirectory()) { - LOG.severe("Unable to create new file $file, because parent directory cannot be created") - return@single - } - if (file.isDirectory) - return@single - - var flag = true - var downloaded = 0 - try { - // check the checksum of file to ensure that the file is not need to re-download. - if (file.exists()) { - val sha1 = DigestUtils.sha1Hex(file.readBytes()) - if (assetObject.hash == sha1) { - ++downloaded - LOG.finest("File $file has been downloaded successfully, skipped downloading") - updateProgress(downloaded, size) - return@single - } - } - } catch (e: IOException) { - LOG.log(Level.WARNING, "Unable to get hash code of file $file", e) - flag = !file.exists() - } - if (flag) - dependencies += FileDownloadTask(url.toURL(), file, assetObject.hash, proxy = dependencyManager.proxy).apply { - title = assetObject.hash - } - } - } -} - -/** - * This task is to save the version json. - * @param repository the game repository - * @param version the **resolved** version - */ -class VersionJSONSaveTask(private val repository: DefaultGameRepository, private val version: Version): Task() { - override fun execute() { - val json = repository.getVersionJson(version.id).absoluteFile - if (!json.makeFile()) - throw IOException("Cannot create file $json") - json.writeText(GSON.toJson(version)) - } -} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameRemote.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameRemote.kt deleted file mode 100644 index 4d7b6f747..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameRemote.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.game - -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.game.ReleaseType -import org.jackhuang.hmcl.util.DEFAULT_LIBRARY_URL -import org.jackhuang.hmcl.util.Immutable -import org.jackhuang.hmcl.util.Validation -import java.util.* - -@Immutable -internal class GameRemoteLatestVersions( - @SerializedName("snapshot") - val snapshot: String, - @SerializedName("release") - val release: String -) - -@Immutable -internal class GameRemoteVersion @JvmOverloads constructor( - @SerializedName("id") - val gameVersion: String = "", - @SerializedName("time") - val time: Date = Date(), - @SerializedName("releaseTime") - val releaseTime: Date = Date(), - @SerializedName("type") - val type: ReleaseType = ReleaseType.UNKNOWN, - @SerializedName("url") - val url: String = "$DEFAULT_LIBRARY_URL$gameVersion/$gameVersion.json" -) : Validation { - override fun validate() { - if (gameVersion.isBlank()) - throw IllegalArgumentException("GameRemoteVersion id cannot be blank") - if (url.isBlank()) - throw IllegalArgumentException("GameRemoteVersion url cannot be blank") - } -} - -@Immutable -internal class GameRemoteVersions( - @SerializedName("versions") - val versions: List = emptyList(), - - @SerializedName("latest") - val latest: GameRemoteLatestVersions? = null -) - -@Immutable -data class GameRemoteVersionTag ( - val type: ReleaseType, - val time: Date -) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameVersionList.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameVersionList.kt deleted file mode 100644 index dfc9cb3fa..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/game/GameVersionList.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.game - -import org.jackhuang.hmcl.download.DownloadProvider -import org.jackhuang.hmcl.download.RemoteVersion -import org.jackhuang.hmcl.download.VersionList -import org.jackhuang.hmcl.task.GetTask -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.util.GSON -import org.jackhuang.hmcl.util.asVersion -import org.jackhuang.hmcl.util.fromJson -import org.jackhuang.hmcl.util.toURL - - -object GameVersionList : VersionList() { - - override fun refreshAsync(downloadProvider: DownloadProvider): Task { - return RefreshTask(downloadProvider) - } - - private class RefreshTask(provider: DownloadProvider) : Task() { - val task = GetTask(provider.versionListURL.toURL()) - override val dependents: Collection = listOf(task) - override fun execute() { - versions.clear() - - val root = GSON.fromJson(task.result!!) ?: return - for (remoteVersion in root.versions) { - val gg = remoteVersion.gameVersion.asVersion() ?: continue - val x = RemoteVersion( - gameVersion = remoteVersion.gameVersion, - selfVersion = remoteVersion.gameVersion, - url = remoteVersion.url, - tag = GameRemoteVersionTag( - type = remoteVersion.type, - time = remoteVersion.releaseTime - ) - ) - versions.put(gg, x) - } - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.kt deleted file mode 100644 index e55962f26..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.liteloader - -import org.jackhuang.hmcl.download.DefaultDependencyManager -import org.jackhuang.hmcl.download.RemoteVersion -import org.jackhuang.hmcl.download.game.GameLibrariesTask -import org.jackhuang.hmcl.game.LibrariesDownloadInfo -import org.jackhuang.hmcl.game.Library -import org.jackhuang.hmcl.game.LibraryDownloadInfo -import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.TaskResult -import org.jackhuang.hmcl.task.then -import org.jackhuang.hmcl.util.merge - -/** - * Note: LiteLoader must be installed after Forge. - */ -class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyManager, - private val gameVersion: String, - private val version: Version, - private val remoteVersion: String): TaskResult() { - private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList - lateinit var remote: RemoteVersion - override val dependents = mutableListOf() - override val dependencies = mutableListOf() - override val id = "version" - - init { - if (!liteLoaderVersionList.loaded) - dependents += liteLoaderVersionList.refreshAsync(dependencyManager.downloadProvider).then { - remote = liteLoaderVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $gameVersion, $remoteVersion not found") - null - } - else { - remote = liteLoaderVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $gameVersion, $remoteVersion not found") - } - } - - override fun execute() { - val library = Library( - groupId = "com.mumfrey", - artifactId = "liteloader", - version = remote.selfVersion, - url = "http://dl.liteloader.com/versions/", - downloads = LibrariesDownloadInfo( - artifact = LibraryDownloadInfo( - url = remote.url - ) - ) - ) - val tempVersion = version.copy(libraries = merge(remote.tag.libraries, listOf(library))) - result = version.copy( - mainClass = "net.minecraft.launchwrapper.Launch", - minecraftArguments = version.minecraftArguments + " --tweakClass " + remote.tag.tweakClass, - libraries = merge(tempVersion.libraries, version.libraries), - logging = emptyMap() - ) - dependencies += GameLibrariesTask(dependencyManager, tempVersion) - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemote.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemote.kt deleted file mode 100644 index 48965f045..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemote.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.liteloader - -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.game.Library -import org.jackhuang.hmcl.util.Immutable - -@Immutable -internal data class LiteLoaderVersionsMeta @JvmOverloads constructor( - @SerializedName("description") - val description: String = "", - @SerializedName("authors") - val authors: String = "", - @SerializedName("url") - val url: String = "" -) - -@Immutable -internal data class LiteLoaderRepository @JvmOverloads constructor( - @SerializedName("stream") - val stream: String = "", - @SerializedName("type") - val type: String = "", - @SerializedName("url") - val url: String = "", - @SerializedName("classifier") - val classifier: String = "" -) - -@Immutable -internal class LiteLoaderVersion @JvmOverloads constructor( - @SerializedName("tweakClass") - val tweakClass: String = "", - @SerializedName("file") - val file: String = "", - @SerializedName("version") - val version: String = "", - @SerializedName("md5") - val md5: String = "", - @SerializedName("timestamp") - val timestamp: String = "", - @SerializedName("lastSuccessfulBuild") - val lastSuccessfulBuild: Int = 0, - @SerializedName("libraries") - val libraries: Collection = emptyList() -) - -@Immutable -internal class LiteLoaderBranch @JvmOverloads constructor( - @SerializedName("libraries") - val libraries: Collection = emptyList(), - @SerializedName("com.mumfrey:liteloader") - val liteLoader: Map = emptyMap() -) - -@Immutable -internal class LiteLoaderGameVersions @JvmOverloads constructor( - @SerializedName("repo") - val repo: LiteLoaderRepository? = null, - @SerializedName("artefacts") - val artifacts: LiteLoaderBranch? = null, - @SerializedName("snapshots") - val snapshots: LiteLoaderBranch? = null -) - -@Immutable -internal class LiteLoaderVersionsRoot @JvmOverloads constructor( - @SerializedName("versions") - val versions: Map = emptyMap(), - @SerializedName("meta") - val meta: LiteLoaderVersionsMeta? = null -) - -@Immutable -data class LiteLoaderRemoteVersionTag ( - val tweakClass: String, - val libraries: Collection -) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.kt deleted file mode 100644 index aab772359..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderVersionList.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.liteloader - -import org.jackhuang.hmcl.download.DownloadProvider -import org.jackhuang.hmcl.download.RemoteVersion -import org.jackhuang.hmcl.download.VersionList -import org.jackhuang.hmcl.task.GetTask -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.util.GSON -import org.jackhuang.hmcl.util.asVersion -import org.jackhuang.hmcl.util.fromJson -import org.jackhuang.hmcl.util.toURL - -object LiteLoaderVersionList : VersionList() { - @JvmField - val LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json" - - override fun refreshAsync(downloadProvider: DownloadProvider): Task { - return RefreshTask(downloadProvider) - } - - internal class RefreshTask(val downloadProvider: DownloadProvider) : Task() { - val task = GetTask(downloadProvider.injectURL(LITELOADER_LIST).toURL()) - override val dependents: Collection = listOf(task) - override fun execute() { - val root = GSON.fromJson(task.result!!) ?: return - versions.clear() - - for ((gameVersion, liteLoader) in root.versions.entries) { - val gg = gameVersion.asVersion() ?: continue - doBranch(gg, gameVersion, liteLoader.repo, liteLoader.artifacts, false) - doBranch(gg, gameVersion, liteLoader.repo, liteLoader.snapshots, true) - } - } - - private fun doBranch(key: String, gameVersion: String, repository: LiteLoaderRepository?, branch: LiteLoaderBranch?, snapshot: Boolean) { - if (branch == null || repository == null) - return - for ((branchName, v) in branch.liteLoader.entries) { - if ("latest" == branchName) - continue - val iv = RemoteVersion( - selfVersion = v.version.replace("SNAPSHOT", "SNAPSHOT-" + v.lastSuccessfulBuild), - gameVersion = gameVersion, - url = if (snapshot) - "http://jenkins.liteloader.com/view/$gameVersion/job/LiteLoader $gameVersion/lastSuccessfulBuild/artifact/build/libs/liteloader-${v.version}-release.jar" - else - downloadProvider.injectURL(repository.url + "com/mumfrey/liteloader/" + gameVersion + "/" + v.file), - tag = LiteLoaderRemoteVersionTag(tweakClass = v.tweakClass, libraries = v.libraries) - ) - - versions.put(key, iv) - } - } - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.kt deleted file mode 100644 index 4ac869d2b..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.optifine - -import org.jackhuang.hmcl.download.DownloadProvider -import org.jackhuang.hmcl.download.RemoteVersion -import org.jackhuang.hmcl.download.VersionList -import org.jackhuang.hmcl.task.GetTask -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.util.GSON -import org.jackhuang.hmcl.util.asVersion -import org.jackhuang.hmcl.util.toURL -import org.jackhuang.hmcl.util.typeOf - -object OptiFineBMCLVersionList : VersionList() { - override fun refreshAsync(downloadProvider: DownloadProvider): Task { - return RefreshTask(downloadProvider) - } - - private class RefreshTask(val downloadProvider: DownloadProvider): Task() { - val task = GetTask("http://bmclapi.bangbang93.com/optifine/versionlist".toURL()) - override val dependents: Collection = listOf(task) - override fun execute() { - versions.clear() - - val duplicates = mutableSetOf() - val root = GSON.fromJson>(task.result!!, typeOf>()) - for (element in root) { - val version = element.type ?: continue - val mirror = "http://bmclapi2.bangbang93.com/optifine/${element.gameVersion}/${element.type}/${element.patch}" - if (duplicates.contains(mirror)) - continue - else - duplicates += mirror - - val gameVersion = element.gameVersion?.asVersion() ?: continue - val remoteVersion = RemoteVersion( - gameVersion = gameVersion, - selfVersion = version, - url = mirror, - tag = Unit - ) - - versions.put(gameVersion, remoteVersion) - } - } - - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineDownloadFormatter.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineDownloadFormatter.kt deleted file mode 100644 index 94014b0b9..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineDownloadFormatter.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.optifine - - -object OptiFineDownloadFormatter \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.kt deleted file mode 100644 index 927d3f634..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.optifine - -import org.jackhuang.hmcl.download.DefaultDependencyManager -import org.jackhuang.hmcl.download.RemoteVersion -import org.jackhuang.hmcl.download.game.GameLibrariesTask -import org.jackhuang.hmcl.game.LibrariesDownloadInfo -import org.jackhuang.hmcl.game.Library -import org.jackhuang.hmcl.game.LibraryDownloadInfo -import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.TaskResult -import org.jackhuang.hmcl.task.then -import org.jackhuang.hmcl.util.merge - -/** - * **Note**: OptiFine should be installed in the end. - */ -class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManager, - private val gameVersion: String, - private val version: Version, - private val remoteVersion: String): TaskResult() { - private val optiFineVersionList = dependencyManager.getVersionList("optifine") - lateinit var remote: RemoteVersion<*> - override val dependents = mutableListOf() - override val dependencies = mutableListOf() - override val id = "version" - - override val reliesOnDependencies = false - - init { - if (!optiFineVersionList.loaded) - dependents += optiFineVersionList.refreshAsync(dependencyManager.downloadProvider) then { - remote = optiFineVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote OptiFine version $gameVersion-$remoteVersion not found") - null - } - else { - remote = optiFineVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote OptiFine version $gameVersion-$remoteVersion not found") - } - } - - override fun execute() { - val library = Library( - groupId = "net.optifine", - artifactId = "optifine", - version = remoteVersion, - lateload = true, - downloads = LibrariesDownloadInfo( - artifact = LibraryDownloadInfo( - path = "net/optifine/optifine/$remoteVersion/optifine-$remoteVersion.jar", - url = remote.url - ) - )) - val libraries = mutableListOf(library) - var arg = version.minecraftArguments!! - if (!arg.contains("FMLTweaker")) - arg += " --tweakClass optifine.OptiFineTweaker" - var mainClass = version.mainClass - if (mainClass == null || !mainClass.startsWith("net.minecraft.launchwrapper.")) { - mainClass = "net.minecraft.launchwrapper.Launch" - libraries.add(0, Library( - groupId = "net.minecraft", - artifactId = "launchwrapper", - version = "1.12" - )) - } - result = version.copy(libraries = merge(version.libraries, libraries), mainClass = mainClass, minecraftArguments = arg) - dependencies += GameLibrariesTask(dependencyManager, version.copy(libraries = libraries)) - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineRemote.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineRemote.kt deleted file mode 100644 index 2f185def9..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineRemote.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.optifine - -import com.google.gson.annotations.SerializedName - -data class OptiFineVersion @JvmOverloads constructor( - @SerializedName("dl") - val downloadLink: String? = null, - @SerializedName("ver") - val version: String? = null, - @SerializedName("date") - val date: String? = null, - @SerializedName("type") - val type: String? = null, - @SerializedName("patch") - val patch: String? = null, - @SerializedName("mirror") - val mirror: String? = null, - @SerializedName("mcversion") - val gameVersion: String? = null -) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineVersionList.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineVersionList.kt deleted file mode 100644 index c6b68e2a7..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/optifine/OptiFineVersionList.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.download.optifine - -import org.jackhuang.hmcl.download.DownloadProvider -import org.jackhuang.hmcl.download.RemoteVersion -import org.jackhuang.hmcl.download.VersionList -import org.jackhuang.hmcl.task.GetTask -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.util.toURL -import org.w3c.dom.Element -import java.io.ByteArrayInputStream -import java.util.regex.Pattern -import javax.xml.parsers.DocumentBuilderFactory - -object OptiFineVersionList : VersionList() { - private val pattern = Pattern.compile("OptiFine (.*?) ") - - override fun refreshAsync(downloadProvider: DownloadProvider): Task { - return RefreshTask(downloadProvider) - } - - private class RefreshTask(val downloadProvider: DownloadProvider) : Task() { - val task = GetTask("http://optifine.net/downloads".toURL()) - override val dependents: Collection = listOf(task) - override fun execute() { - versions.clear() - - val html = task.result!!.replace(" ", " ").replace(">", ">").replace("<", "<").replace("
", "
") - - val factory = DocumentBuilderFactory.newInstance() - val db = factory.newDocumentBuilder() - val doc = db.parse(ByteArrayInputStream(html.toByteArray())) - val r = doc.documentElement - val tables = r.getElementsByTagName("table") - for (i in 0 until tables.length) { - val e = tables.item(i) as Element - if ("downloadTable" == e.getAttribute("class")) { - val tr = e.getElementsByTagName("tr") - for (k in 0 until tr.length) { - val downloadLine = (tr.item(k) as Element).getElementsByTagName("td") - var url: String? = null - var version: String? = null - for (j in 0 until downloadLine.length) { - val td = downloadLine.item(j) as Element - if (td.getAttribute("class")?.startsWith("downloadLineMirror") ?: false) - url = (td.getElementsByTagName("a").item(0) as Element).getAttribute("href") - if (td.getAttribute("class")?.startsWith("downloadLineFile") ?: false) - version = td.textContent - } - val matcher = pattern.matcher(version) - var gameVersion: String? = null - while (matcher.find()) - gameVersion = matcher.group(1) - if (gameVersion == null || version == null || url == null) continue - val remoteVersion = RemoteVersion( - gameVersion = gameVersion, - selfVersion = version, - url = url, - tag = Unit - ) - versions.put(gameVersion, remoteVersion) - } - } - } - - } - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/Event.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/Event.kt deleted file mode 100644 index c8e1a751e..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/Event.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.event - -import java.util.* - -open class Event(source: Any) : EventObject(source) { - /** - * true if this event is canceled. - * - * @throws UnsupportedOperationException if trying to cancel a non-cancelable event. - */ - var isCanceled = false - set(value) { - if (!isCancelable) - throw UnsupportedOperationException("Attempted to cancel a non-cancelable event: ${this.javaClass}") - field = value - } - - /** - * Retutns the value set as the result of this event - */ - var result = Result.DEFAULT - set(value) { - if (!hasResult) - throw UnsupportedOperationException("Attempted to set result on a no result event: ${this.javaClass} of type.") - field = value - } - - /** - * true if this Event this cancelable. - */ - open val isCancelable = false - - /** - * true if this event has a significant result. - */ - open val hasResult = false - - enum class Result { - DENY, - DEFAULT, - ALLOW - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventManager.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventManager.kt deleted file mode 100644 index 46b637fdc..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/EventManager.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.event - -import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.util.SimpleMultimap -import java.util.* - -class EventManager @JvmOverloads constructor(val scheduler: Scheduler = Scheduler.IMMEDIATE) { - private val handlers = SimpleMultimap Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet) - private val handlers2 = SimpleMultimap Unit>({ EnumMap(EventPriority::class.java) }, ::HashSet) - - fun register(func: (T) -> Unit, priority: EventPriority = EventPriority.NORMAL) { - if (!handlers[priority].contains(func)) - handlers.put(priority, func) - } - - fun register(func: () -> Unit, priority: EventPriority = EventPriority.NORMAL) { - if (!handlers2[priority].contains(func)) - handlers2.put(priority, func) - } - - fun unregister(func: (T) -> Unit) { - handlers.remove(func) - } - - fun unregister(func: () -> Unit) { - handlers2.remove(func) - } - - fun fireEvent(event: T) { - scheduler.schedule { - for (priority in EventPriority.values()) { - for (handler in handlers[priority]) - handler(event) - for (handler in handlers2[priority]) - handler() - } - } - } - - operator fun plusAssign(func: (T) -> Unit) = register(func) - operator fun plusAssign(func: () -> Unit) = register(func) - operator fun minusAssign(func: (T) -> Unit) = unregister(func) - operator fun minusAssign(func: () -> Unit) = unregister(func) - operator fun invoke(event: T) = fireEvent(event) -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/Events.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/Events.kt deleted file mode 100644 index 7290a04d9..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/event/Events.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.event - -import org.jackhuang.hmcl.util.ManagedProcess -import java.util.* - -/** - * This event gets fired when loading versions in a .minecraft folder. - *
- * This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS] - * - * @param source [org.jackhuang.hmcl.game.GameRepository] - */ -class RefreshingVersionsEvent(source: Any) : EventObject(source) - -/** - * This event gets fired when all the versions in .minecraft folder are loaded. - *
- * This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS] - * @param source [org.jackhuang.hmcl.game.GameRepository] - * @author huangyuhui - */ -class RefreshedVersionsEvent(source: Any) : EventObject(source) - -/** - * This event gets fired when a minecraft version has been loaded. - *
- * This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS] - * @param source [org.jackhuang.hmcl.game.GameRepository] - * @param version the version id. - * @author huangyuhui - */ -class LoadedOneVersionEvent(source: Any, val version: String) : EventObject(source) - -/** - * This event gets fired when a JavaProcess exited abnormally and the exit code is not zero. - *

- * This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS] - * @param source [org.jackhuang.hmcl.launch.ExitWaiter] - * @param value The process that exited abnormally. - * @author huangyuhui - */ -class ProcessExitedAbnormallyEvent(source: Any, val value: ManagedProcess) : EventObject(source) - -/** - * This event gets fired when minecraft process exited successfully and the exit code is 0. - *
- * This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS] - * @param source [org.jackhuang.hmcl.launch.ExitWaiter] - * @param value minecraft process - * @author huangyuhui - */ -class ProcessStoppedEvent(source: Any, val value: ManagedProcess) : EventObject(source) - -/** - * This event gets fired when we launch the JVM and it got crashed. - *
- * This event is fired on the [org.jackhuang.hmcl.event.EVENT_BUS] - * @param source [org.jackhuang.hmcl.launch.ExitWaiter] - * @param value the crashed process. - * @author huangyuhui - */ -class JVMLaunchFailedEvent(source: Any, val value: ManagedProcess) : EventObject(source) diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Argument.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Argument.kt deleted file mode 100644 index f761adb9c..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Argument.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.* -import java.lang.reflect.Type - -interface Argument { - - /** - * Parse this argument in form: ${key name} or simply a string. - * - * @param keys the parse map - * @param features the map that contains some features such as 'is_demo_user', 'has_custom_resolution' - * @return parsed argument element, empty if this argument is ignored and will not be added. - */ - fun toString(keys: Map, features: Map): List - - companion object Serializer : JsonDeserializer, JsonSerializer { - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Argument = - if (json.isJsonPrimitive) - StringArgument(json.asString) - else - context.deserialize(json, RuledArgument::class.java) - - override fun serialize(src: Argument, typeOfSrc: Type, context: JsonSerializationContext): JsonElement = - when (src) { - is StringArgument -> JsonPrimitive(src.argument) - is RuledArgument -> context.serialize(src, RuledArgument::class.java) - else -> throw AssertionError("Unrecognized argument type: $src") - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Arguments.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Arguments.kt deleted file mode 100644 index 91bcfdc8c..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Arguments.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.util.OS -import org.jackhuang.hmcl.game.CompatibilityRule.* -import org.jackhuang.hmcl.util.merge - -class Arguments @JvmOverloads constructor( - @SerializedName("game") - val game: List? = null, - @SerializedName("jvm") - val jvm: List? = null -) { - companion object { - fun mergeArguments(a: Arguments?, b: Arguments?): Arguments? { - if (a == null && b == null) return null - else if (a == null) return b - else if (b == null) return a - else return Arguments( - game = merge(a.game, b.game), - jvm = merge(a.jvm, b.jvm) - ) - } - - fun parseStringArguments(arguments: List, keys: Map, features: Map = emptyMap()): List { - return arguments.flatMap { StringArgument(it).toString(keys, features) } - } - - fun parseArguments(arguments: List, keys: Map, features: Map = emptyMap()): List { - return arguments.flatMap { it.toString(keys, features) } - } - - val DEFAULT_JVM_ARGUMENTS = listOf( - RuledArgument(listOf(CompatibilityRule(Action.ALLOW, OSRestriction(OS.OSX))), listOf("-XstartOnFirstThread")), - RuledArgument(listOf(CompatibilityRule(Action.ALLOW, OSRestriction(OS.WINDOWS))), listOf("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump")), - RuledArgument(listOf(CompatibilityRule(Action.ALLOW, OSRestriction(OS.WINDOWS, "^10\\."))), listOf("-Dos.name=Windows 10", "-Dos.version=10.0")), - StringArgument("-Djava.library.path=\${natives_directory}"), - StringArgument("-Dminecraft.launcher.brand=\${launcher_name}"), - StringArgument("-Dminecraft.launcher.version=\${launcher_version}"), - StringArgument("-cp"), - StringArgument("\${classpath}") - ) - - val DEFAULT_GAME_ARGUMENTS = listOf( - RuledArgument(listOf(CompatibilityRule(Action.ALLOW, features = mapOf("has_custom_resolution" to true))), listOf("--width", "\${resolution_width}", "--height", "\${resolution_height}")) - ) - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetIndexInfo.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetIndexInfo.kt deleted file mode 100644 index 9e69f7e1a..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/AssetIndexInfo.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import org.jackhuang.hmcl.util.Immutable - -@Immutable -class AssetIndexInfo @JvmOverloads constructor( - url: String = "", - sha1: String? = null, - size: Int = 0, - id: String = "", - val totalSize: Long = 0 -) : IdDownloadInfo(url, sha1, size, id) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ClassicVersion.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ClassicVersion.kt deleted file mode 100644 index a66e877fb..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ClassicVersion.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import org.jackhuang.hmcl.util.Immutable -import java.io.File - -/** - * The Minecraft version for 1.5.x and earlier. - */ -@Immutable -class ClassicVersion : Version( - mainClass = "net.minecraft.client.Minecraft", - id = "Classic", - type = ReleaseType.UNKNOWN, - minecraftArguments = "\${auth_player_name} \${auth_session} --workDir \${game_directory}", - libraries = listOf( - ClassicLibrary("lwjgl"), - ClassicLibrary("jinput"), - ClassicLibrary("lwjgl_util") - ) -) { - @Immutable - private class ClassicLibrary(name: String) : - Library(groupId = "", artifactId = "", version = "", - downloads = LibrariesDownloadInfo( - artifact = LibraryDownloadInfo(path = "bin/$name.jar") - ) - ) - - companion object { - /** - * Check if this directory is an old style Minecraft repository. - */ - fun hasClassicVersion(baseDirectory: File): Boolean { - val file = File(baseDirectory, "bin") - if (!file.exists()) return false - if (!File(file, "lwjgl.jar").exists()) return false - if (!File(file, "jinput.jar").exists()) return false - if (!File(file, "lwjgl_util.jar").exists()) return false - return true - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/CompatibilityRule.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/CompatibilityRule.kt deleted file mode 100644 index c6e274ebd..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/CompatibilityRule.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.util.Immutable -import org.jackhuang.hmcl.util.OS -import org.jackhuang.hmcl.util.ignoreThrowable -import java.util.regex.Pattern - -@Immutable -data class CompatibilityRule @JvmOverloads constructor( - val action: Action = CompatibilityRule.Action.ALLOW, - val os: OSRestriction? = null, - val features: Map? = null -) { - - fun getAppliedAction(supportedFeatures: Map): Action? { - if (os != null && !os.allow()) return null - if (features != null) { - features.entries.forEach { (key, value) -> - if (supportedFeatures[key] != value) - return null - } - } - return action - } - - companion object { - fun appliesToCurrentEnvironment(rules: Collection?, features: Map = emptyMap()): Boolean { - if (rules == null) - return true - var action = CompatibilityRule.Action.DISALLOW - for (rule in rules) { - val thisAction = rule.getAppliedAction(features) - if (thisAction != null) action = thisAction - } - return action == CompatibilityRule.Action.ALLOW - } - } - - enum class Action { - @SerializedName("allow") - ALLOW, - @SerializedName("disallow") - DISALLOW - } - - @Immutable - data class OSRestriction( - val name: OS = OS.UNKNOWN, - val version: String? = null, - val arch: String? = null - ) { - fun allow(): Boolean { - if (name != OS.UNKNOWN && name != OS.CURRENT_OS) - return false - if (version != null) { - ignoreThrowable { - if (!Pattern.compile(version).matcher(OS.SYSTEM_VERSION).matches()) - return false - } - } - if (arch != null) - ignoreThrowable { - if (!Pattern.compile(arch).matcher(OS.SYSTEM_ARCH).matches()) - return false - } - return true - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DefaultGameRepository.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DefaultGameRepository.kt deleted file mode 100644 index 7f8b35a44..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DefaultGameRepository.kt +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.JsonSyntaxException -import org.jackhuang.hmcl.event.EVENT_BUS -import org.jackhuang.hmcl.event.LoadedOneVersionEvent -import org.jackhuang.hmcl.event.RefreshedVersionsEvent -import org.jackhuang.hmcl.event.RefreshingVersionsEvent -import org.jackhuang.hmcl.util.GSON -import org.jackhuang.hmcl.util.LOG -import org.jackhuang.hmcl.util.fromJson -import org.jackhuang.hmcl.util.listFilesByExtension -import java.io.File -import java.io.IOException -import java.util.* -import java.util.logging.Level - -/** - * An implementation of classic Minecraft game repository. - */ -open class DefaultGameRepository(var baseDirectory: File): GameRepository { - protected val versions: MutableMap = TreeMap() - var isLoaded: Boolean = false - protected set - - override fun hasVersion(id: String) = versions.containsKey(id) - override fun getVersion(id: String): Version { - return versions[id] ?: throw VersionNotFoundException("Version '$id' does not exist.") - } - override fun getVersionCount() = versions.size - override fun getVersions() = versions.values - override fun getLibraryFile(version: Version, lib: Library) = baseDirectory.resolve("libraries/${lib.path}") - override fun getRunDirectory(id: String) = baseDirectory - override fun getVersionJar(version: Version): File { - val v = version.resolve(this) - val id = v.jar ?: v.id - return getVersionRoot(id).resolve("$id.jar") - } - - /** - * {@inheritsDoc} - * @return something like ".minecraft/versions//-natives" - */ - override fun getNativeDirectory(id: String) = getVersionRoot(id).resolve("$id-natives") - override fun getVersionRoot(id: String) = baseDirectory.resolve("versions/$id") - open fun getVersionJson(id: String) = getVersionRoot(id).resolve("$id.json") - - @Throws(IOException::class, JsonSyntaxException::class) - open fun readVersionJson(id: String): Version? = readVersionJson(getVersionJson(id)) - - @Throws(IOException::class, JsonSyntaxException::class) - open fun readVersionJson(file: File): Version? = GSON.fromJson(file.readText()) - - override fun renameVersion(from: String, to: String): Boolean { - try { - val fromVersion = getVersion(from) - val fromDir = getVersionRoot(from) - val toDir = getVersionRoot(to) - if (!fromDir.renameTo(toDir)) - return false - val toJson = File(toDir, "$to.json") - val toJar = File(toDir, "$to.jar") - if (!File(toDir, "$from.json").renameTo(toJson) || - !File(toDir, "$from.jar").renameTo(toJar)) { - toDir.renameTo(fromDir) // simple recovery - return false - } - toJson.writeText(GSON.toJson(fromVersion.copy(id = to))) - return true - } catch (e: IOException) { - return false - } catch (e2: JsonSyntaxException) { - return false - } catch (e: VersionNotFoundException) { - return false - } - } - - open fun removeVersionFromDisk(name: String): Boolean { - val file = getVersionRoot(name) - if (!file.exists()) - return true - versions.remove(name) - return file.deleteRecursively() - } - - protected open fun refreshVersionsImpl() { - versions.clear() - - if (ClassicVersion.hasClassicVersion(baseDirectory)) { - val version = ClassicVersion() - versions[version.id] = version - } - - baseDirectory.resolve("versions").listFiles()?.filter { it.isDirectory }?.forEach tryVersion@{ dir -> - val id = dir.name - val json = dir.resolve("$id.json") - - // If user renamed the json file by mistake or created the json file in a wrong name, - // we will find the only json and rename it to correct name. - if (!json.exists()) { - val jsons = dir.listFilesByExtension("json") - if (jsons.size == 1) - jsons.single().renameTo(json) - } - - var version: Version - try { - version = readVersionJson(json)!! - } catch(e: Exception) { // JsonSyntaxException or IOException or NullPointerException(!!) - // TODO: auto making up for the missing json - // TODO: and even asking for removing the redundant version folder. - return@tryVersion - } - - if (id != version.id) { - version = version.copy(id = id) - try { - json.writeText(GSON.toJson(version)) - } catch(e: Exception) { - LOG.warning("Ignoring version $id because wrong id ${version.id} is set and cannot correct it.") - return@tryVersion - } - } - - versions[id] = version - EVENT_BUS.fireEvent(LoadedOneVersionEvent(this, id)) - } - isLoaded = true - } - - final override fun refreshVersions() { - EVENT_BUS.fireEvent(RefreshingVersionsEvent(this)) - refreshVersionsImpl() - EVENT_BUS.fireEvent(RefreshedVersionsEvent(this)) - } - - override fun getAssetIndex(version: String, assetId: String): AssetIndex { - return GSON.fromJson(getIndexFile(version, assetId).readText())!! - } - - override fun getActualAssetDirectory(version: String, assetId: String): File { - try { - return reconstructAssets(version, assetId) - } catch (e: Exception) { - LOG.log(Level.SEVERE, "Unable to reconstruct asset directory", e) - return getAssetDirectory(version, assetId) - } - } - - override fun getAssetDirectory(version: String, assetId: String): File = - baseDirectory.resolve("assets") - - @Throws(IOException::class) - override fun getAssetObject(version: String, assetId: String, name: String): File { - try { - return getAssetObject(version, assetId, getAssetIndex(version, assetId).objects[name]!!) - } catch (e: Exception) { - throw IOException("Asset index file malformed", e) - } - } - - override fun getAssetObject(version: String, assetId: String, obj: AssetObject): File = - getAssetObject(version, getAssetDirectory(version, assetId), obj) - - open fun getAssetObject(version: String, assetDir: File, obj: AssetObject): File { - return assetDir.resolve("objects/${obj.location}") - } - - override fun getIndexFile(version: String, assetId: String): File = - getAssetDirectory(version, assetId).resolve("indexes/$assetId.json") - - override fun getLoggingObject(version: String, assetId: String, loggingInfo: LoggingInfo): File = - getAssetDirectory(version, assetId).resolve("log_configs/${loggingInfo.file.id}") - - @Throws(IOException::class, JsonSyntaxException::class) - protected open fun reconstructAssets(version: String, assetId: String): File { - val assetsDir = getAssetDirectory(version, assetId) - val indexFile: File = getIndexFile(version, assetId) - val virtualRoot = assetsDir.resolve("virtual").resolve(assetId) - - if (!indexFile.isFile) { - return assetsDir - } - - val assetIndexContent = indexFile.readText() - val index = GSON.fromJson(assetIndexContent, AssetIndex::class.java) ?: return assetsDir - - if (index.virtual) { - var cnt = 0 - LOG.info("Reconstructing virtual assets folder at " + virtualRoot) - val tot = index.objects.entries.size - for ((location, assetObject) in index.objects.entries) { - val target = File(virtualRoot, location) - val original = getAssetObject(version, assetsDir, assetObject) - if (original.exists()) { - cnt++ - if (!target.isFile) - original.copyTo(target) - } - } - // If the scale new format existent file is lower then 0.1, use the old format. - if (cnt * 10 < tot) - return assetsDir - else - return virtualRoot - } - - return assetsDir - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DownloadInfo.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DownloadInfo.kt deleted file mode 100644 index e655ef92a..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DownloadInfo.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.JsonSyntaxException -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.util.Immutable -import org.jackhuang.hmcl.util.Validation - -@Immutable -open class DownloadInfo @JvmOverloads constructor( - @SerializedName("url") - val url: String = "", - @SerializedName("sha1") - val sha1: String? = null, - @SerializedName("size") - val size: Int = 0 -): Validation { - override fun validate() { - if (url.isBlank()) - throw JsonSyntaxException("DownloadInfo url can not be null") - } -} - -@Immutable -open class IdDownloadInfo @JvmOverloads constructor( - url: String = "", - sha1: String? = null, - size: Int = 0, - @SerializedName("id") - val id: String = "" -): DownloadInfo(url, sha1, size) { - override fun validate() { - super.validate() - - if (id.isBlank()) - throw JsonSyntaxException("IdDownloadInfo id can not be null") - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DownloadType.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DownloadType.kt deleted file mode 100644 index 6c475f361..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DownloadType.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.annotations.SerializedName - -enum class DownloadType { - @SerializedName("client") - CLIENT, - @SerializedName("server") - SERVER, - @SerializedName("windows-server") - WINDOWS_SERVER -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ExtractRules.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ExtractRules.kt deleted file mode 100644 index 04bb2542a..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/ExtractRules.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.annotations.SerializedName -import java.util.* - -class ExtractRules @JvmOverloads constructor(exclude: List = emptyList()) { - val exclude: List get() = Collections.unmodifiableList(excludeImpl) - - @SerializedName("exclude") - private val excludeImpl: MutableList = LinkedList(exclude) - - fun shouldExtract(path: String): Boolean = - exclude.none { path.startsWith(it) } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameException.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameException.kt deleted file mode 100644 index f629c1ce9..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameException.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -open class GameException : Exception { - constructor() : super() {} - constructor(message: String) : super(message) {} - constructor(message: String, cause: Throwable) : super(message, cause) {} -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameVersion.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameVersion.kt deleted file mode 100644 index b2d87b4d4..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/GameVersion.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import org.jackhuang.hmcl.util.closeQuietly -import org.jackhuang.hmcl.util.readFullyAsByteArray -import java.io.File -import java.io.IOException -import java.util.zip.ZipEntry -import java.util.zip.ZipFile - -private fun lessThan32(b: ByteArray, startIndex: Int): Int { - for (i in startIndex until b.size) - if (b[i] < 32) - return i - return -1 -} - -fun matchArray(a: ByteArray, b: ByteArray): Int { - for (i in 0 until a.size - b.size) { - var j = 1 - for (k in b.indices) { - if (b[k] == a[i + k]) - continue - j = 0 - break - } - if (j != 0) - return i - } - return -1 -} - -@Throws(IOException::class) -private fun getVersionOfOldMinecraft(file: ZipFile, entry: ZipEntry): String? { - val tmp = file.getInputStream(entry).readFullyAsByteArray() - - val bytes = "Minecraft Minecraft ".toByteArray(Charsets.US_ASCII) - var j = matchArray(tmp, bytes) - if (j < 0) { - return null - } - val i = j + bytes.size - j = lessThan32(tmp, i) - - if (j < 0) { - return null - } - val ver = String(tmp, i, j - i, Charsets.US_ASCII) - return ver -} - -@Throws(IOException::class) -private fun getVersionOfNewMinecraft(file: ZipFile, entry: ZipEntry): String? { - val tmp = file.getInputStream(entry).readFullyAsByteArray() - - var str = "-server.txt".toByteArray(charset("ASCII")) - var j = matchArray(tmp, str) - if (j < 0) { - return null - } - var i = j + str.size - i += 11 - j = lessThan32(tmp, i) - if (j < 0) { - return null - } - val result = String(tmp, i, j - i, Charsets.US_ASCII) - - val ch = result[0] - // 1.8.1+ - if (ch < '0' || ch > '9') { - str = "Can't keep up! Did the system time change, or is the server overloaded?".toByteArray(charset("ASCII")) - j = matchArray(tmp, str) - if (j < 0) { - return null - } - i = -1 - while (j > 0) { - if (tmp[j] in 48..57) { - i = j - break - } - j-- - } - if (i == -1) { - return null - } - var k = i - if (tmp[i + 1] >= 'a'.toInt() && tmp[i + 1] <= 'z'.toInt()) - i++ - while (tmp[k] in 48..57 || tmp[k] == '-'.toByte() || tmp[k] == '.'.toByte() || tmp[k] >= 97 && tmp[k] <= 'z'.toByte()) - k-- - k++ - return String(tmp, k, i - k + 1, Charsets.US_ASCII) - } - return result -} - -fun minecraftVersion(file: File?): String? { - if (file == null || !file.isFile || !file.canRead()) { - return null - } - var f: ZipFile? = null - try { - f = ZipFile(file) - val minecraft = f - .getEntry("net/minecraft/client/Minecraft.class") - if (minecraft != null) - return getVersionOfOldMinecraft(f, minecraft) - val main = f.getEntry("net/minecraft/client/main/Main.class") - val minecraftserver = f.getEntry("net/minecraft/server/MinecraftServer.class") - if (main != null && minecraftserver != null) - return getVersionOfNewMinecraft(f, minecraftserver) - return null - } catch (e: IOException) { - return null - } finally { - f?.closeQuietly() - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LaunchOptions.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LaunchOptions.kt deleted file mode 100644 index 97df046f3..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LaunchOptions.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import org.jackhuang.hmcl.util.JavaVersion -import java.io.File -import java.io.Serializable - -data class LaunchOptions( - /** - * The game directory - */ - val gameDir: File, - - /** - * The Java Environment that Minecraft runs on. - */ - val java: JavaVersion = JavaVersion.fromCurrentEnvironment(), - - /** - * Will shown in the left bottom corner of the main menu of Minecraft. - * null if use the id of launch version. - */ - val versionName: String? = null, - - /** - * Don't know what the hell this is. - */ - var profileName: String? = null, - - /** - * User custom additional minecraft command line arguments. - */ - val minecraftArgs: String? = null, - - /** - * User custom additional java virtual machine command line arguments. - */ - val javaArgs: String? = null, - - /** - * The minimum memory that the JVM can allocate. - */ - val minMemory: Int? = null, - - /** - * The maximum memory that the JVM can allocate. - */ - val maxMemory: Int? = null, - - /** - * The maximum metaspace memory that the JVM can allocate. - * For Java 7 -XX:PermSize and Java 8 -XX:MetaspaceSize - * Containing class instances. - */ - val metaspace: Int? = null, - - /** - * The initial game window width - */ - val width: Int? = null, - - /** - * The initial game window height - */ - val height: Int? = null, - - /** - * Is inital game window fullscreen. - */ - val fullscreen: Boolean = false, - - /** - * The server ip that will connect to when enter game main menu. - */ - val serverIp: String? = null, - - /** - * i.e. optirun - */ - val wrapper: String? = null, - - /** - * The host of the proxy address - */ - val proxyHost: String? = null, - - /** - * the port of the proxy address. - */ - val proxyPort: String? = null, - - /** - * The user name of the proxy, optional. - */ - val proxyUser: String? = null, - - /** - * The password of the proxy, optional - */ - val proxyPass: String? = null, - - /** - * Prevent game launcher from generating default JVM arguments like max memory. - */ - val noGeneratedJVMArgs: Boolean = false, - - /** - * Called command line before launching the game. - */ - val precalledCommand: String? = null - -) : Serializable \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LibrariesDownloadInfo.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LibrariesDownloadInfo.kt deleted file mode 100644 index b30c39ac2..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LibrariesDownloadInfo.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.util.Immutable -import java.util.* -import kotlin.collections.HashMap - -@Immutable -class LibrariesDownloadInfo @JvmOverloads constructor( - @SerializedName("artifact") - val artifact: LibraryDownloadInfo? = null, - classifier_: Map? = null -) { - val classifiers: Map? - get() = Collections.unmodifiableMap(classifiersImpl) - - @SerializedName("classifiers") - private var classifiersImpl: MutableMap? = - if (classifier_ == null || classifier_.isEmpty()) null - else HashMap(classifier_) -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Library.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Library.kt deleted file mode 100644 index 48ca65888..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Library.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.* -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.util.* -import java.lang.reflect.Type - -/** - * A class that describes a Minecraft dependency. - */ -@Immutable -open class Library @JvmOverloads constructor( - val groupId: String, - val artifactId: String, - val version: String, - classifier_: String? = null, - @SerializedName("url") - private val url: String? = null, - @SerializedName("downloads") - private val downloads: LibrariesDownloadInfo? = null, - @SerializedName("extract") - val extract: ExtractRules? = null, - @SerializedName("lateload") - val lateload: Boolean = false, - private val natives: Map? = null, - private val rules: List? = null -) { - - val appliesToCurrentEnvironment: Boolean = CompatibilityRule.appliesToCurrentEnvironment(rules) - val isNative: Boolean = natives != null && appliesToCurrentEnvironment - val download: LibraryDownloadInfo - val classifier: String? = classifier_ ?: natives?.get(OS.CURRENT_OS)?.replace("\${arch}", Platform.PLATFORM.bit) - val path: String - - init { - - var temp: LibraryDownloadInfo? = null - if (downloads != null) { - if (isNative) - temp = downloads.classifiers?.get(classifier) - else - temp = downloads.artifact - } - path = temp?.path ?: "${groupId.replace(".", "/")}/$artifactId/$version/$artifactId-$version" + (if (classifier == null) "" else "-$classifier") + ".jar" - download = LibraryDownloadInfo( - url = temp?.url ?: (url ?: DEFAULT_LIBRARY_URL) + path, - path = path, - size = temp?.size ?: 0, - sha1 = temp?.sha1 - ) - } - - override fun toString() = "Library[$groupId:$artifactId:$version]" - - companion object LibrarySerializer : JsonDeserializer, JsonSerializer { - fun fromName(name: String, url: String? = null, downloads: LibrariesDownloadInfo? = null, extract: ExtractRules? = null, natives: Map? = null, rules: List? = null): Library { - val arr = name.split(":".toRegex(), 4) - if (arr.size != 3 && arr.size != 4) - throw IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version.") - return Library( - groupId = arr[0].replace("\\", "/"), - artifactId = arr[1], - version = arr[2], - classifier_ = arr.getOrNull(3), - url = url, - downloads = downloads, - extract = extract, - natives = natives, - rules = rules - ) - } - - override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext): Library? { - if (json == null || json == JsonNull.INSTANCE) - return null - val jsonObject = json.asJsonObject - if (jsonObject["name"] == null) - throw JsonParseException("Library name not found.") - return fromName(jsonObject["name"].asString, jsonObject["url"]?.asString, context.deserialize(jsonObject["downloads"], LibrariesDownloadInfo::class.java), - context.deserialize(jsonObject["extract"], ExtractRules::class.java), - context.deserialize(jsonObject["natives"], typeOf>()), - context.deserialize(jsonObject["rules"], typeOf>())) - } - - override fun serialize(src: Library?, typeOfSrc: Type?, context: JsonSerializationContext): JsonElement { - if (src == null) return JsonNull.INSTANCE - val obj = JsonObject() - obj.addProperty("name", "${src.groupId}:${src.artifactId}:${src.version}") - obj.addProperty("url", src.url) - obj.add("downloads", context.serialize(src.downloads)) - obj.add("extract", context.serialize(src.extract)) - obj.add("natives", context.serialize(src.natives, typeOf>())) - obj.add("rules", context.serialize(src.rules, typeOf>())) - return obj - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LibraryDownloadInfo.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LibraryDownloadInfo.kt deleted file mode 100644 index f47393e9c..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/LibraryDownloadInfo.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.util.Immutable - -@Immutable -class LibraryDownloadInfo @JvmOverloads constructor( - url: String = "", - sha1: String? = null, - size: Int = 0, - @SerializedName("path") - val path: String? = null -): DownloadInfo(url, sha1, size) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/RuledArgument.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/RuledArgument.kt deleted file mode 100644 index bb3b12806..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/RuledArgument.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.* -import com.google.gson.reflect.TypeToken -import org.jackhuang.hmcl.util.typeOf -import java.lang.reflect.Type - - -class RuledArgument @JvmOverloads constructor( - val rules: List? = null, - val value: List? = null) : Argument { - - override fun toString(keys: Map, features: Map): List = - if (CompatibilityRule.appliesToCurrentEnvironment(rules)) - value?.map { StringArgument(it).toString(keys, features).single() } ?: emptyList() - else - emptyList() - - companion object Serializer : JsonSerializer, JsonDeserializer { - override fun serialize(src: RuledArgument, typeOfSrc: Type, context: JsonSerializationContext) = - JsonObject().apply { - add("rules", context.serialize(src.rules)) - add("value", context.serialize(src.value)) - } - - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): RuledArgument { - val obj = json.asJsonObject - return RuledArgument( - rules = context.deserialize(obj["rules"], typeOf>()), - value = if (obj["value"].isJsonPrimitive) - listOf(obj["value"].asString) - else - context.deserialize(obj["value"], typeOf>()) - ) - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/StringArgument.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/StringArgument.kt deleted file mode 100644 index 73fdace4f..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/StringArgument.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import java.util.* -import java.util.regex.Pattern - -class StringArgument(val argument: String) : Argument { - - override fun toString(keys: Map, features: Map): List { - var res = argument - val pattern = Pattern.compile("\\$\\{(.*?)\\}") - val m = pattern.matcher(argument) - while (m.find()) { - val entry = m.group() - res = res.replace(entry, keys.getOrDefault(entry, entry)) - } - return Collections.singletonList(res) - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Version.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Version.kt deleted file mode 100644 index e60edb8c8..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Version.kt +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -import com.google.gson.JsonParseException -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.util.* -import java.util.* - -@Immutable -open class Version @JvmOverloads constructor( - @SerializedName("minecraftArguments") - val minecraftArguments: String? = null, - @SerializedName("arguments") - val arguments: Arguments? = null, - @SerializedName("mainClass") - val mainClass: String? = null, - @SerializedName("time") - val time: Date = Date(), - @SerializedName("id") - val id: String = "", - @SerializedName("type") - val type: ReleaseType = ReleaseType.UNKNOWN, - @SerializedName("releaseTime") - val releaseTime: Date = Date(), - @SerializedName("jar") - val jar: String? = null, - @SerializedName("inheritsFrom") - val inheritsFrom: String? = null, - @SerializedName("assets") - private val assets: String? = null, - @SerializedName("minimumLauncherVersion") - val minimumLauncherVersion: Int = 0, - @SerializedName("assetIndex") - private val assetIndex: AssetIndexInfo? = null, - - libraries: List = emptyList(), - compatibilityRules: List? = null, - downloads: Map? = null, - logging: Map? = null -): Comparable, Validation { - val downloads: Map? get() = unmodifiableMap(downloadsImpl) - - @SerializedName("downloads") - private val downloadsImpl: MutableMap? = copyMap(downloads) - - val logging: Map? get() = unmodifiableMap(loggingImpl) - - @SerializedName("logging") - private val loggingImpl: MutableMap? = copyMap(logging) - - val libraries: List get() = Collections.unmodifiableList(librariesImpl) - - @SerializedName("libraries") - private val librariesImpl: MutableList = LinkedList(libraries) - - val compatibilityRules: List? get() = unmodifiableList(compatibilityRulesImpl) - - @SerializedName("compatibilityRules") - private val compatibilityRulesImpl: MutableList? = copyList(compatibilityRules) - - val download: DownloadInfo - get() { - val client = downloads?.get(DownloadType.CLIENT) - val jar = this.jar ?: this.id - if (client == null) { - return DownloadInfo("$DEFAULT_VERSION_DOWNLOAD_URL$jar/$jar.jar") - } else { - return client - } - } - - val actualAssetIndex: AssetIndexInfo - get() { - val id = assets ?: "legacy" - return assetIndex ?: AssetIndexInfo(id = id, url = "$DEFAULT_INDEX_URL$id.json") - } - - fun appliesToCurrentEnvironment() = CompatibilityRule.appliesToCurrentEnvironment(compatibilityRules) - - override fun hashCode(): Int = id.hashCode() - override fun equals(other: Any?): Boolean = - if (other is Version) Objects.equals(this.id, other.id) - else false - override fun compareTo(other: Version) = id.compareTo(other.id) - open fun install(id: String): Boolean { - return false - } - - /** - * Resolve given version - * - * @throws CircleDependencyException - */ - fun resolve(provider: VersionProvider): Version = - resolve(provider, HashSet()) - - protected open fun resolve(provider: VersionProvider, resolvedSoFar: MutableSet): Version { - if (this.inheritsFrom == null) - return this - if (!resolvedSoFar.add(this.id)) { - LOG.warning("Found circular dependency versions: $resolvedSoFar") - return this - } - - // It is supposed to auto install an version in getVersion. - val parent = provider.getVersion(this.inheritsFrom).resolve(provider, resolvedSoFar) - return Version( - id = this.id, - jar = this.jar ?: parent.jar, - time = this.time, - type = this.type, - assets = this.assets ?: parent.assets, - logging = this.logging ?: parent.logging, - mainClass = this.mainClass ?: parent.mainClass, - libraries = merge(this.libraries, parent.libraries), - downloads = this.downloads ?: parent.downloads, - assetIndex = this.assetIndex ?: parent.assetIndex, - arguments = Arguments.mergeArguments(parent.arguments, this.arguments), - releaseTime = this.releaseTime, - inheritsFrom = null, - compatibilityRules = merge(this.compatibilityRules, parent.compatibilityRules), - minecraftArguments = this.minecraftArguments ?: parent.minecraftArguments, - minimumLauncherVersion = maxOf(minimumLauncherVersion, parent.minimumLauncherVersion) - ) - } - - fun copy( - minecraftArguments: String? = this.minecraftArguments, - arguments: Arguments? = this.arguments, - mainClass: String? = this.mainClass, - time: Date = this.time, - releaseTime: Date = this.releaseTime, - id: String = this.id, - type: ReleaseType = this.type, - jar: String? = this.jar, - inheritsFrom: String? = this.inheritsFrom, - assets: String? = this.assets, - minimumLauncherVersion: Int = this.minimumLauncherVersion, - assetIndex: AssetIndexInfo? = this.assetIndex, - libraries: List = this.librariesImpl, - compatibilityRules: List? = this.compatibilityRulesImpl, - downloads: Map? = this.downloads, - logging: Map? = this.logging) = - Version(minecraftArguments, - arguments, - mainClass, - time, - id, - type, - releaseTime, - jar, - inheritsFrom, - assets, - minimumLauncherVersion, - assetIndex, - libraries, - compatibilityRules, - downloads, - logging - ) - - override fun validate() { - if (id.isBlank()) - throw JsonParseException("Version ID cannot be blank") - (downloadsImpl as Map<*, *>?)?.forEach { (key, value) -> - if (key !is DownloadType) - throw JsonParseException("Version downloads key must be DownloadType") - if (value !is DownloadInfo) - throw JsonParseException("Version downloads value must be DownloadInfo") - } - (loggingImpl as Map<*, *>?)?.forEach { (key, value) -> - if (key !is DownloadType) - throw JsonParseException("Version logging key must be DownloadType") - if (value !is LoggingInfo) - throw JsonParseException("Version logging value must be DownloadInfo") - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/VersionNotFoundException.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/VersionNotFoundException.kt deleted file mode 100644 index 3d57899fb..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/VersionNotFoundException.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.game - -class VersionNotFoundException : GameException { - constructor() : super() {} - constructor(message: String) : super(message) {} - constructor(message: String, cause: Throwable) : super(message, cause) {} -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt deleted file mode 100644 index 1614e9cff..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.launch - -import org.jackhuang.hmcl.auth.AuthInfo -import org.jackhuang.hmcl.game.* -import org.jackhuang.hmcl.task.TaskResult -import org.jackhuang.hmcl.util.* -import java.io.File -import java.io.IOException -import java.util.* -import kotlin.concurrent.thread - -/** - * @param versionId The version to be launched. - * @param account The user account - * @param options The launching configuration - */ -open class DefaultLauncher @JvmOverloads constructor(repository: GameRepository, versionId: String, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true) - : Launcher(repository, versionId, account, options, listener, isDaemon) { - - protected val native: File by lazy { repository.getNativeDirectory(version.id) } - - init { - if (version.inheritsFrom != null) - throw IllegalArgumentException("Version must be resolved") - if (version.minecraftArguments == null) - throw NullPointerException("Version minecraft argument can not be null") - if (version.mainClass == null || version.mainClass.isBlank()) - throw NullPointerException("Version main class can not be null") - } - - protected open val defaultJVMArguments = Arguments.DEFAULT_JVM_ARGUMENTS - protected open val defaultGameArguments = Arguments.DEFAULT_GAME_ARGUMENTS - protected open val forbidden = mapOf("-Xincgc" to { options.java.version >= JavaVersion.JAVA_9 }) - - /** - * Note: the [account] must have logged in when calling this property - */ - override val rawCommandLine: List by lazy { - val res = LinkedList() - - // Executable - if (options.wrapper != null && options.wrapper.isNotBlank()) - res.add(options.wrapper) - - res.add(options.java.binary.toString()) - - if (options.javaArgs != null && options.javaArgs.isNotBlank()) - res.addAll(options.javaArgs.tokenize()) - - // JVM Args - if (!options.noGeneratedJVMArgs) { - appendJvmArgs(res) - - res.add("-Dminecraft.client.jar=${repository.getVersionJar(version)}") - - if (OS.CURRENT_OS == OS.OSX) { - res.add("-Xdock:name=Minecraft ${version.id}") - res.add("-Xdock:icon=" + repository.getAssetObject(version.id, version.actualAssetIndex.id, "icons/minecraft.icns").absolutePath) - } - - val logging = version.logging - if (logging != null) { - val loggingInfo = logging[DownloadType.CLIENT] - if (loggingInfo != null) { - val loggingFile = repository.getLoggingObject(version.id, version.actualAssetIndex.id, loggingInfo) - if (loggingFile.exists()) - res.add(loggingInfo.argument.replace("\${path}", loggingFile.absolutePath)) - } - } - - if (OS.CURRENT_OS != OS.WINDOWS) - res.add("-Duser.home=${options.gameDir.parent}") - - if (options.java.version >= JavaVersion.JAVA_7) - res.add("-XX:+UseG1GC") - - if (options.metaspace != null && options.metaspace > 0) { - if (options.java.version < JavaVersion.JAVA_8) - res.add("-XX:PermSize=${options.metaspace}m") - else - res.add("-XX:MetaspaceSize=${options.metaspace}m") - } - - res.add("-XX:-UseAdaptiveSizePolicy") - res.add("-XX:-OmitStackTraceInFastThrow") - res.add("-Xmn128m") - - if (options.maxMemory != null && options.maxMemory > 0) - res.add("-Xmx${options.maxMemory}m") - if (options.minMemory != null && options.minMemory > 0) - res.add("-Xms${options.minMemory}m") - - res.add("-Dfml.ignoreInvalidMinecraftCertificates=true") - res.add("-Dfml.ignorePatchDiscrepancies=true") - } - - val lateload = LinkedList() - val classpath = StringBuilder() - for (library in version.libraries) - if (library.appliesToCurrentEnvironment && !library.isNative) { - val f = repository.getLibraryFile(version, library) - if (f.exists() && f.isFile) { - if (library.lateload) - lateload += f - else classpath.append(f.absolutePath).append(OS.PATH_SEPARATOR) - } - } - for (library in lateload) - classpath.append(library.absolutePath).append(OS.PATH_SEPARATOR) - - val jar = repository.getVersionJar(version) - if (!jar.exists() || !jar.isFile) - throw GameException("Minecraft jar does not exist") - classpath.append(jar.absolutePath) - - // Provided Minecraft arguments - val gameAssets = repository.getActualAssetDirectory(version.id, version.actualAssetIndex.id) - val configuration = getConfigurations() - configuration["\${classpath}"] = classpath.toString() - configuration["\${natives_directory}"] = native.absolutePath - configuration["\${game_assets}"] = gameAssets.absolutePath - configuration["\${assets_root}"] = gameAssets.absolutePath - - res.addAll(Arguments.parseArguments(version.arguments?.jvm ?: defaultJVMArguments, configuration)) - res.add(version.mainClass!!) - - val features = getFeatures() - res.addAll(Arguments.parseArguments(version.arguments?.game ?: defaultGameArguments, configuration, features)) - res.addAll(Arguments.parseStringArguments(version.minecraftArguments?.tokenize() ?: emptyList(), configuration)) - - // Optional Minecraft arguments - if (options.height != null && options.height != 0 && options.width != null && options.width != 0) { - res.add("--height") - res.add(options.height.toString()) - res.add("--width") - res.add(options.width.toString()) - } - - if (options.serverIp != null && options.serverIp.isNotBlank()) { - val args = options.serverIp.split(":") - res.add("--server") - res.add(args[0]) - res.add("--port") - res.add(if (args.size > 1) args[1] else "25565") - } - - if (options.fullscreen) - res.add("--fullscreen") - - if (options.proxyHost != null && options.proxyHost.isNotBlank() && - options.proxyPort != null && options.proxyPort.isNotBlank()) { - res.add("--proxyHost") - res.add(options.proxyHost) - res.add("--proxyPort") - res.add(options.proxyPort) - if (options.proxyUser != null && options.proxyUser.isNotBlank() && - options.proxyPass != null && options.proxyPass.isNotBlank()) { - res.add("--proxyUser") - res.add(options.proxyUser) - res.add("--proxyPass") - res.add(options.proxyPass) - } - } - - if (options.minecraftArgs != null && options.minecraftArgs.isNotBlank()) - res.addAll(options.minecraftArgs.tokenize()) - - res.filter { !(forbidden[it]?.invoke() ?: false) } - } - - /** - * Do something here. - * i.e. - * -Dminecraft.launcher.version= - * -Dminecraft.launcher.brand= - * -Dlog4j.configurationFile=) {} - - open fun decompressNatives() { - version.libraries.filter { it.isNative }.forEach { library -> - @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") - repository.getLibraryFile(version, library).uncompressTo( - dest = native, - callback = if (library.extract == null) null - else library.extract!!::shouldExtract, - ignoreExistentFile = false) - } - } - - open fun getConfigurations(): MutableMap = mutableMapOf( - "\${auth_player_name}" to account.username, - "\${auth_session}" to account.authToken, - "\${auth_access_token}" to account.authToken, - "\${auth_uuid}" to account.userId, - "\${version_name}" to (options.versionName ?: version.id), - "\${profile_name}" to (options.profileName ?: "Minecraft"), - "\${version_type}" to version.type.id, - "\${game_directory}" to repository.getRunDirectory(version.id).absolutePath, - "\${user_type}" to account.userType.toString().toLowerCase(), - "\${assets_index_name}" to version.actualAssetIndex.id, - "\${user_properties}" to account.userProperties - ) - - open fun getFeatures(): MutableMap = mutableMapOf( - "has_custom_resolution" to (options.height != null && options.height != 0 && options.width != null && options.width != 0) - ) - - override fun launch(): ManagedProcess { - - // To guarantee that when failed to generate code, we will not call precalled command - val builder = ProcessBuilder(rawCommandLine) - - decompressNatives() - - if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) { - try { - val process = Runtime.getRuntime().exec(options.precalledCommand) - if (process.isAlive) - process.waitFor() - } catch (e: IOException) { - // TODO: alert precalledCommand is wrong. - // rethrow InterruptedException - } - } - - builder.directory(repository.getRunDirectory(version.id)) - .environment().put("APPDATA", options.gameDir.absoluteFile.parent) - val p = ManagedProcess(builder.start(), rawCommandLine) - if (listener == null) - startMonitors(p) - else - startMonitors(p, listener, isDaemon) - return p - } - - fun launchAsync(): TaskResult { - return object : TaskResult() { - override val id = LAUNCH_ASYNC_ID - override fun execute() { - result = launch() - } - } - } - - override fun makeLaunchScript(file: String): File { - val isWindows = OS.WINDOWS == OS.CURRENT_OS - val scriptFile = File(file + (if (isWindows) ".bat" else ".sh")) - if (!scriptFile.makeFile()) - throw IOException("Script file: $scriptFile cannot be created.") - scriptFile.bufferedWriter().use { writer -> - if (isWindows) { - writer.write("@echo off") - writer.newLine() - writer.write("set APPDATA=" + options.gameDir.parent) - writer.newLine() - writer.write("cd /D %APPDATA%") - writer.newLine() - } - if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) { - writer.write(options.precalledCommand) - writer.newLine() - } - writer.write(makeCommand(rawCommandLine)) - } - if (!scriptFile.setExecutable(true)) - throw IOException("Cannot make script file '$scriptFile' executable.") - return scriptFile - } - - private fun startMonitors(managedProcess: ManagedProcess) { - managedProcess.relatedThreads += thread(name = "stdout-pump", isDaemon = true, block = StreamPump(managedProcess.process.inputStream)::run) - managedProcess.relatedThreads += thread(name = "stderr-pump", isDaemon = true, block = StreamPump(managedProcess.process.errorStream)::run) - } - - private fun startMonitors(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) { - val enablesLoggingInfo = version.logging?.containsKey(DownloadType.CLIENT) ?: false - if (enablesLoggingInfo) - startMonitorsWithLoggingInfo(managedProcess, processListener, isDaemon) - else - startMonitorsWithoutLoggingInfo(managedProcess, processListener, isDaemon) - } - - private fun startMonitorsWithLoggingInfo(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) { - processListener.setProcess(managedProcess) - val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); managedProcess.lines += line }.apply { start() } - managedProcess.relatedThreads += logHandler - val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.inputStream, { logHandler.newLine(it) })::run) - managedProcess.relatedThreads += stdout - val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); managedProcess.lines += it })::run) - managedProcess.relatedThreads += stderr - managedProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(managedProcess, listOf(stdout, stderr), { exitCode, exitType -> logHandler.onStopped(); processListener.onExit(exitCode, exitType) })::run) - } - - private fun startMonitorsWithoutLoggingInfo(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) { - processListener.setProcess(managedProcess) - val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.inputStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.guessLevel(it) ?: Log4jLevel.INFO); managedProcess.lines += it })::run) - managedProcess.relatedThreads += stdout - val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); managedProcess.lines += it })::run) - managedProcess.relatedThreads += stderr - managedProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(managedProcess, listOf(stdout, stderr), { exitCode, exitType -> processListener.onExit(exitCode, exitType) })::run) - } - - companion object { - const val LAUNCH_ASYNC_ID = "process" - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/ExitWaiter.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/ExitWaiter.kt deleted file mode 100644 index 6df3c32a9..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/ExitWaiter.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.launch - -import org.jackhuang.hmcl.event.EVENT_BUS -import org.jackhuang.hmcl.event.JVMLaunchFailedEvent -import org.jackhuang.hmcl.event.ProcessExitedAbnormallyEvent -import org.jackhuang.hmcl.event.ProcessStoppedEvent -import org.jackhuang.hmcl.util.ManagedProcess -import org.jackhuang.hmcl.util.containsOne -import org.jackhuang.hmcl.util.guessLogLineError - -/** - * @param process the process to wait for - * @param watcher the callback that will be called after process stops. - */ -internal class ExitWaiter(val process: ManagedProcess, val joins: Collection, val watcher: (Int, ProcessListener.ExitType) -> Unit) : Runnable { - override fun run() { - try { - val exitCode = process.process.waitFor() - - joins.forEach(Thread::join) - - val errorLines = process.lines.filter(::guessLogLineError) - val exitType: ProcessListener.ExitType - // LaunchWrapper will catch the exception logged and will exit normally. - if (exitCode != 0 || errorLines.containsOne("Unable to launch")) { - EVENT_BUS.fireEvent(ProcessExitedAbnormallyEvent(this, process)) - exitType = ProcessListener.ExitType.APPLICATION_ERROR - } else if (exitCode != 0 && errorLines.containsOne( - "Could not create the Java Virtual Machine.", - "Error occurred during initialization of VM", - "A fatal exception has occurred. Program will exit.", - "Unable to launch")) { - EVENT_BUS.fireEvent(JVMLaunchFailedEvent(this, process)) - exitType = ProcessListener.ExitType.JVM_ERROR - } else - exitType = ProcessListener.ExitType.NORMAL - - EVENT_BUS.fireEvent(ProcessStoppedEvent(this, process)) - - watcher(exitCode, exitType) - } catch (e: InterruptedException) { - watcher(1, ProcessListener.ExitType.NORMAL) - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/Launcher.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/Launcher.kt deleted file mode 100644 index 46a566eb8..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/Launcher.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.launch - -import org.jackhuang.hmcl.auth.AuthInfo -import org.jackhuang.hmcl.game.GameRepository -import org.jackhuang.hmcl.game.LaunchOptions -import org.jackhuang.hmcl.game.Version -import org.jackhuang.hmcl.util.ManagedProcess -import java.io.File - -abstract class Launcher @JvmOverloads constructor( - protected val repository: GameRepository, - protected val versionId: String, - protected val account: AuthInfo, - protected val options: LaunchOptions, - protected val listener: ProcessListener? = null, - protected val isDaemon: Boolean = true) { - - val version: Version = repository.getVersion(versionId).resolve(repository) - abstract val rawCommandLine: List - abstract fun launch(): ManagedProcess - - /** - * @param file The file path without extension - * @return the actual file - */ - abstract fun makeLaunchScript(file: String): File -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/Log4jHandler.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/Log4jHandler.kt deleted file mode 100644 index 78b70e901..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/Log4jHandler.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.launch - -import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.util.Log4jLevel -import org.jackhuang.hmcl.util.OS -import org.xml.sax.Attributes -import org.xml.sax.InputSource -import org.xml.sax.helpers.DefaultHandler -import org.xml.sax.helpers.XMLReaderFactory -import java.io.InterruptedIOException -import java.io.PipedInputStream -import java.io.PipedOutputStream -import java.text.SimpleDateFormat -import java.util.* -import java.util.concurrent.atomic.AtomicBoolean - -/** - * This class is to parse log4j classic XML layout logging, since only vanilla Minecraft will enable this layout. - * Also supports plain logs. - */ -internal class Log4jHandler(private val callback: (String, Log4jLevel) -> Unit) : Thread() { - val reader = XMLReaderFactory.createXMLReader().apply { - contentHandler = Log4jHandlerImpl() - } - private val outputStream = PipedOutputStream() - private val inputStream = PipedInputStream(outputStream) - private val interrupted = AtomicBoolean(false) - - override fun run() { - name = "log4j-handler" - newLine("") - try { - reader.parse(InputSource(inputStream)) - } catch (e: InterruptedIOException) { - // Game has been interrupted. - interrupted.set(true) - } - } - - /** - * Should be called to stop [Log4jHandler] manually. - */ - fun onStopped() { - if (interrupted.get()) - return - Scheduler.NEW_THREAD.schedule { - if (!interrupted.get()) { - newLine("")?.get() - outputStream.close() - join() - } - }!!.get() - } - - /** - * New XML line. - */ - fun newLine(content: String) = - Scheduler.COMPUTATION.schedule { - var log = content - if (!log.trim().startsWith("<")) - log = "", "") + "]]>" - outputStream.write((log + OS.LINE_SEPARATOR) - .replace("log4j:Event", "log4j_Event") - .replace("log4j:Message", "log4j_Message") - .replace("log4j:Throwable", "log4j_Throwable") - .toByteArray()) - outputStream.flush() - } - - inner class Log4jHandlerImpl : DefaultHandler() { - private val df = SimpleDateFormat("HH:mm:ss") - - var date = "" - var thread = "" - var logger = "" - var message: StringBuilder? = null - var l: Log4jLevel? = null - var readingMessage = false - - override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) { - when (localName) { - "log4j_Event" -> { - message = StringBuilder() - val d = Date(attributes.getValue("timestamp").toLong()) - date = df.format(d) - l = try { - Log4jLevel.valueOf(attributes.getValue("level")) - } catch (e: IllegalArgumentException) { - Log4jLevel.INFO - } - - thread = attributes.getValue("thread") - logger = attributes.getValue("logger") - if ("STDERR" == logger) - l = Log4jLevel.ERROR - } - "log4j_Message" -> readingMessage = true - "log4j_Throwable" -> {} - } - } - - override fun endElement(uri: String?, localName: String?, qName: String?) { - when (localName) { - "log4j_Event" -> callback("[" + date + "] [" + thread + "/" + l!!.name + "] [" + logger + "] " + message.toString(), l!!) - "log4j_Message" -> readingMessage = false - } - } - - override fun characters(ch: CharArray, start: Int, length: Int) { - val line = String(ch, start, length) - if (line.trim { it <= ' ' }.isEmpty()) return - if (readingMessage) - message!!.append(line).append(OS.LINE_SEPARATOR) - else - callback(line, Log4jLevel.guessLevel(line) ?: Log4jLevel.INFO) - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/StreamPump.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/StreamPump.kt deleted file mode 100644 index 5f21e21bc..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/StreamPump.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.launch - -import org.jackhuang.hmcl.util.LOG -import org.jackhuang.hmcl.util.SYSTEM_CHARSET -import java.io.IOException -import java.io.InputStream -import java.util.logging.Level - -/** - * Pump the given input stream. - * @param inputStream the input stream to pump - * @param callback receives each line - * - */ -internal class StreamPump @JvmOverloads constructor( - private val inputStream: InputStream, - private val callback: (String) -> Unit = {} -) : Runnable { - - override fun run() { - try { - inputStream.bufferedReader(SYSTEM_CHARSET).useLines { lines -> - for (line in lines) { - if (Thread.currentThread().isInterrupted) { - Thread.currentThread().interrupt() - break - } - callback(line) - } - } - } catch (e: IOException) { - LOG.log(Level.SEVERE, "An error occurred when reading stream", e) - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt deleted file mode 100644 index 8f3c0913d..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.mod - -import com.google.gson.JsonParseException -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.download.DefaultDependencyManager -import org.jackhuang.hmcl.download.DependencyManager -import org.jackhuang.hmcl.game.GameException -import org.jackhuang.hmcl.task.FileDownloadTask -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.task -import org.jackhuang.hmcl.util.* -import java.io.File -import java.io.IOException -import java.net.URL -import java.util.logging.Level - -class CurseForgeModpackManifest @JvmOverloads constructor( - @SerializedName("manifestType") - val manifestType: String = MINECRAFT_MODPACK, - @SerializedName("manifestVersion") - val manifestVersion: Int = 1, - @SerializedName("name") - val name: String = "", - @SerializedName("version") - val version: String = "1.0", - @SerializedName("author") - val author: String = "", - @SerializedName("overrides") - val overrides: String = "overrides", - @SerializedName("minecraft") - val minecraft: CurseForgeModpackManifestMinecraft = CurseForgeModpackManifestMinecraft(), - @SerializedName("files") - val files: List = emptyList() -): Validation { - override fun validate() { - check(manifestType == MINECRAFT_MODPACK, { "Only support Minecraft modpack" }) - } - - companion object { - val MINECRAFT_MODPACK = "minecraftModpack" - } -} - -class CurseForgeModpackManifestMinecraft @JvmOverloads constructor( - @SerializedName("version") - val gameVersion: String = "", - @SerializedName("modLoaders") - val modLoaders: List = emptyList() -): Validation { - override fun validate() { - check(gameVersion.isNotBlank()) - } -} - -class CurseForgeModpackManifestModLoader @JvmOverloads constructor( - @SerializedName("id") - val id: String = "", - @SerializedName("primary") - val primary: Boolean = false -): Validation { - override fun validate() { - check(id.isNotBlank(), { "Curse Forge modpack manifest Mod loader id cannot be blank." }) - } -} - -class CurseForgeModpackManifestFile @JvmOverloads constructor( - @SerializedName("projectID") - val projectID: Int = 0, - @SerializedName("fileID") - val fileID: Int = 0, - @SerializedName("fileName") - var fileName: String = "", - @SerializedName("required") - val required: Boolean = true -): Validation { - override fun validate() { - check(projectID != 0) - check(fileID != 0) - } - - val url: URL get() = "https://minecraft.curseforge.com/projects/$projectID/files/$fileID/download".toURL() -} - -/** - * @param f the CurseForge modpack file. - * @throws IOException if the file is not a valid zip file. - * @throws JsonParseException if the manifest.json is missing or malformed. - * @return the manifest. - */ -@Throws(IOException::class, JsonParseException::class) -fun readCurseForgeModpackManifest(f: File): Modpack { - val json = f.readTextZipEntry("manifest.json") - val manifest = GSON.fromJson(json) - ?: throw JsonParseException("`manifest.json` not found. Not a valid CurseForge modpack.") - return Modpack( - name = manifest.name, - version = manifest.version, - author = manifest.author, - gameVersion = manifest.minecraft.gameVersion, - description = f.readTextZipEntryQuietly("modlist.html") ?: "No description", - manifest = manifest) -} - -/** - * Install a downloaded CurseForge modpack. - * - * @param dependencyManager the dependency manager. - * @param zipFile the CurseForge modpack file. - * @param manifest The manifest content of given CurseForge modpack. - * @param name the new version name - * @see readCurseForgeModpackManifest - */ -class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDependencyManager, private val zipFile: File, private val manifest: CurseForgeModpackManifest, private val name: String): Task() { - val repository = dependencyManager.repository - init { - check(!repository.hasVersion(name), { "Version $name already exists." }) - } - - val root = repository.getVersionRoot(name) - val run = repository.getRunDirectory(name) - override val dependents = mutableListOf() - override val dependencies = mutableListOf() - init { - val builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.minecraft.gameVersion) - manifest.minecraft.modLoaders.forEach { - if (it.id.startsWith("forge-")) - builder.version("forge", it.id.substring("forge-".length)) - } - dependents += builder.buildAsync() - } - - override fun execute() { - zipFile.uncompressTo(run, subDirectory = manifest.overrides) - - var finished = 0 - for (f in manifest.files) { - try { - f.fileName = f.url.detectFileName(dependencyManager.proxy) - dependencies += FileDownloadTask(f.url, run.resolve("mods").resolve(f.fileName), proxy = dependencyManager.proxy) - } catch (e: IOException) { - // Because in China, the CurseForge is too difficult to visit. - // So if failed, ignore it and retry next time. - } - ++finished - updateProgress(1.0 * finished / manifest.files.size) - } - - root.resolve("manifest.json").writeText(GSON.toJson(manifest)) - } -} - -/** - * Complete the CurseForge version. - * - * @param dependencyManager the dependency manager. - * @param version the existent and physical version. - */ -class CurseForgeModpackCompletionTask(dependencyManager: DependencyManager, version: String): Task() { - val repository = dependencyManager.repository - val run = repository.getRunDirectory(version) - private var manifest: CurseForgeModpackManifest? - private val proxy = dependencyManager.proxy - override val dependents = mutableListOf() - override val dependencies = mutableListOf() - - init { - try { - val manifestFile = repository.getVersionRoot(version).resolve("manifest.json") - if (!manifestFile.exists()) manifest = null - else { - manifest = GSON.fromJson(manifestFile.readText())!! - - // Because in China, CurseForge is too difficult to visit, - // caching the file name is necessary. - for (f in manifest!!.files) { - if (f.fileName.isBlank()) - dependents += task { f.fileName = f.url.detectFileName(proxy) } - } - } - } catch (e: Exception) { - LOG.log(Level.WARNING, "Unable to read CurseForge modpack manifest.json", e) - manifest = null - } - } - - override fun execute() { - if (manifest == null) return - for (f in manifest!!.files) { - if (f.fileName.isBlank()) - throw GameException("Unable to download mod, cannot continue") - val file = run.resolve("mods").resolve(f.fileName) - if (!file.exists()) - dependencies += FileDownloadTask(f.url, file, proxy = proxy) - } - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ForgeModMetadata.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ForgeModMetadata.kt deleted file mode 100644 index 14bd42ed0..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ForgeModMetadata.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.mod - -import com.google.gson.JsonParseException -import com.google.gson.annotations.SerializedName -import org.apache.commons.compress.archivers.zip.ZipFile -import org.jackhuang.hmcl.util.GSON -import org.jackhuang.hmcl.util.parseParams -import org.jackhuang.hmcl.util.readFullyAsString -import org.jackhuang.hmcl.util.typeOf -import java.io.File - -class ForgeModMetadata @JvmOverloads internal constructor( - @SerializedName("modid") - val modId: String = "", - val name: String = "", - val description: String = "", - val author: String = "", - val version: String = "", - val mcversion: String = "", - val url: String = "", - val updateUrl: String = "", - val credits: String = "", - val authorList: Array = emptyArray(), - val authors: Array = emptyArray() -) { - - companion object { - /** - * Read Forge mod ModInfo. - */ - fun fromFile(modFile: File): ModInfo { - ZipFile(modFile).use { - val entry = it.getEntry("mcmod.info") ?: throw JsonParseException("File $modFile is not a Forge mod.") - val modList: List? = GSON.fromJson(it.getInputStream(entry).readFullyAsString(), typeOf>()) - val metadata = modList?.firstOrNull() ?: throw JsonParseException("Mod $modFile 'mcmod.info' is malformed") - var authors: String = metadata.author - if (authors.isBlank() && metadata.authors.isNotEmpty()) { - authors = parseParams("", metadata.authors, ", ") - } - if (authors.isBlank() && metadata.authorList.isNotEmpty()) { - authors = parseParams("", metadata.authorList, ", ") - } - if (authors.isBlank()) - authors = metadata.credits - return ModInfo( - file = modFile, - name = metadata.name, - description = metadata.description, - authors = authors, - version = metadata.version, - mcversion = metadata.mcversion, - url = if (metadata.url.isBlank()) metadata.updateUrl else metadata.url - ) - } - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/LiteModMetadata.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/LiteModMetadata.kt deleted file mode 100644 index 54af4f0dc..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/LiteModMetadata.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.mod - -import com.google.gson.JsonParseException -import org.jackhuang.hmcl.util.GSON -import org.jackhuang.hmcl.util.fromJson -import org.jackhuang.hmcl.util.readFullyAsString -import java.io.File -import java.util.zip.ZipFile - -class LiteModMetadata @JvmOverloads internal constructor( - val name: String = "", - val version: String = "", - val mcversion: String = "", - val revision: String = "", - val author: String = "", - val classTransformerClasses: String = "", - val description: String = "", - val modpackName: String = "", - val modpackVersion: String = "", - val checkUpdateUrl: String = "", - val updateURI: String = "" -) { - - companion object { - /** - * Read LiteLoader mod ModInfo. - */ - fun fromFile(modFile: File): ModInfo { - ZipFile(modFile).use { - val entry = it.getEntry("litemod.json") - requireNotNull(entry, { "File $modFile is not a LiteLoader mod." }) - val modList: LiteModMetadata? = GSON.fromJson(it.getInputStream(entry).readFullyAsString()) - val metadata = modList ?: throw JsonParseException("Mod $modFile 'litemod.json' is malformed") - return ModInfo( - file = modFile, - name = metadata.name, - description = metadata.description, - authors = metadata.author, - version = metadata.version, - mcversion = metadata.mcversion, - url = metadata.updateURI - ) - } - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModInfo.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModInfo.kt deleted file mode 100644 index 073c02b8a..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModInfo.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.mod - -import org.jackhuang.hmcl.util.ImmediateBooleanProperty -import org.jackhuang.hmcl.util.getValue -import org.jackhuang.hmcl.util.setValue -import java.io.File - -class ModInfo @JvmOverloads constructor( - var file: File, - val name: String, - val description: String = "", - val authors: String = "unknown", - val version: String = "unknown", - val mcversion: String = "unknown", - val url: String = "" -): Comparable { - val activeProperty = object : ImmediateBooleanProperty(this, "active", file.extension != DISABLED_EXTENSION) { - override fun invalidated() { - val f = file.absoluteFile - val newf: File - if (f.extension == DISABLED_EXTENSION) - newf = File(f.parentFile, f.nameWithoutExtension) - else - newf = File(f.parentFile, f.name + ".disabled") - if (f.renameTo(newf)) - file = newf - } - } - @JvmName("activeProperty") get - - var isActive: Boolean by activeProperty - - val fileName: String = (if (isActive) file.name else file.nameWithoutExtension).substringBeforeLast(".") - - override fun compareTo(other: ModInfo): Int { - return fileName.compareTo(other.fileName) - } - - companion object { - val DISABLED_EXTENSION = "disabled" - - fun isFileMod(file: File): Boolean { - var name = file.name - val disabled = name.endsWith(".disabled") - if (disabled) - name = name.substringBeforeLast(".disabled") - return name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith("litemod") - } - - fun fromFile(modFile: File): ModInfo { - val file = if (modFile.extension == DISABLED_EXTENSION) - modFile.absoluteFile.parentFile.resolve(modFile.nameWithoutExtension) - else modFile - val description: String - if (file.extension == "zip" || file.extension == "jar") - try { - return ForgeModMetadata.fromFile(modFile) - } catch (ignore: Exception) { - description = "May be Forge mod" - } - - else if (file.extension == "litemod") - try { - return LiteModMetadata.fromFile(modFile) - } catch (ignore: Exception) { - description = "May be LiteLoader mod" - } - else throw IllegalArgumentException("File $modFile is not mod") - - return ModInfo(file = modFile, name = modFile.nameWithoutExtension, description = description) - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModManager.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModManager.kt deleted file mode 100644 index 721fc29af..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/ModManager.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.mod - -import org.jackhuang.hmcl.game.GameRepository -import org.jackhuang.hmcl.util.SimpleMultimap -import org.jackhuang.hmcl.util.asVersion -import org.jackhuang.hmcl.util.ignoreException -import org.jackhuang.hmcl.util.makeDirectory -import java.io.File -import java.io.IOException -import java.util.* - -class ModManager(private val repository: GameRepository) { - private val modCache = SimpleMultimap(::HashMap, ::TreeSet) - - fun refreshMods(id: String): Collection { - modCache.removeAll(id) - val modsDirectory = repository.getRunDirectory(id).resolve("mods") - val puter = { modFile: File -> ignoreException { modCache.put(id, ModInfo.fromFile(modFile)) } } - modsDirectory.listFiles()?.forEach { modFile -> - if (modFile.isDirectory && modFile.name.asVersion() != null) - modFile.listFiles()?.forEach(puter) - puter(modFile) - } - return modCache[id] - } - - fun getMods(id: String) : Collection { - if (!modCache.containsKey(id)) - refreshMods(id) - return modCache[id] - } - - fun addMod(id: String, file: File) { - if (!ModInfo.isFileMod(file)) - throw IllegalArgumentException("File $file is not a valid mod file.") - - if (!modCache.containsKey(id)) - refreshMods(id) - - val modsDirectory = repository.getRunDirectory(id).resolve("mods") - if (!modsDirectory.makeDirectory()) - throw IOException("Cannot make directory $modsDirectory") - - val newFile = modsDirectory.resolve(file.name) - file.copyTo(newFile) - - modCache.put(id, ModInfo.fromFile(newFile)) - } - - fun removeMods(id: String, vararg modInfos: ModInfo): Boolean = - modInfos.fold(true, { acc, modInfo -> acc && modInfo.file.delete() }).apply { refreshMods(id) } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt deleted file mode 100644 index 86f829ede..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.mod - -data class Modpack @JvmOverloads constructor( - val name: String = "", - val author: String? = null, - val version: String? = null, - val gameVersion: String? = null, - val description: String? = null, - val manifest: Any? = null -) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt deleted file mode 100644 index d767ac521..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.mod - -import com.google.gson.annotations.SerializedName -import org.apache.commons.compress.archivers.zip.ZipFile -import org.jackhuang.hmcl.download.DefaultDependencyManager -import org.jackhuang.hmcl.download.game.VersionJSONSaveTask -import org.jackhuang.hmcl.game.Library -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.util.* -import java.io.File -import java.io.IOException -import java.io.InputStream -import java.util.* - -class InstancePatch @JvmOverloads constructor( - val name: String = "", - val version: String = "", - @SerializedName("mcVersion") - val gameVersion: String = "", - val mainClass: String = "", - val fileId: String = "", - @SerializedName("+tweakers") - val tweakers: List = emptyList(), - @SerializedName("+libraries") - val libraries: List = emptyList() -) - -class InstanceConfiguration(defaultName: String, contentStream: InputStream) { - /** - * The instance's name - */ - val name: String // name - - /** - * The game version of the instance - */ - val gameVersion: String // IntendedVersion - - /** - * The permanent generation size of JVM. - */ - val permGen: Int? // PermGen - - /** - * The command to launch JVM. - */ - val wrapperCommand: String? // WrapperCommand - - /** - * The command that will be executed before game launches. - */ - val preLaunchCommand: String? // PreLaunchCommand - - /** - * The command that will be executed after game exits. - */ - val postExitCommand: String? // PostExitCommand - - /** - * The description of the instance - */ - val notes: String? // notes - - /** - * JVM installation location - */ - val javaPath: String? // JavaPath - - /** - * The JVM's arguments - */ - val jvmArgs: String? // JvmArgs - - /** - * True if Minecraft will start in fullscreen mode. - */ - val fullscreen: Boolean // LaunchMaximized - - /** - * The initial width of the game window. - */ - val width: Int? // MinecraftWinWidth - - /** - * The initial height of the game window. - */ - val height: Int? // MinecraftWinHeight - - /** - * The maximum memory that JVM can allocate. - */ - val maxMemory: Int? // MaxMemAlloc - - /** - * The minimum memory that JVM can allocate. - */ - val minMemory: Int? // MinMemAlloc - - /** - * True if show the console window when game launches. - */ - val showConsole: Boolean // ShowConsole - - /** - * True if show the console window when game crashes. - */ - val showConsoleOnError: Boolean // ShowConsoleOnError - - /** - * True if closes the console window when game stops. - */ - val autoCloseConsole: Boolean // AutoCloseConsole - - /** - * True if [maxMemory], [minMemory], [permGen] will come info force. - */ - val overrideMemory: Boolean // OverrideMemory - - /** - * True if [javaPath] will come info force. - */ - val overrideJavaLocation: Boolean // OverrideJavaLocation - - /** - * True if [jvmArgs] will come info force. - */ - val overrideJavaArgs: Boolean // OverrideJavaArgs - - /** - * True if [showConsole], [showConsoleOnError], [autoCloseConsole] will come into force. - */ - val overrideConsole: Boolean // OverrideConsole - - /** - * True if [preLaunchCommand], [postExitCommand], [wrapperCommand] will come into force. - */ - val overrideCommands: Boolean // OverrideCommands - - /** - * True if [height], [width], [fullscreen] will come into force. - */ - val overrideWindow: Boolean // OverrideWindow - - init { - val p = Properties() - p.load(contentStream) - - autoCloseConsole = p.getProperty("AutoCloseConsole") == "true" - gameVersion = p.getProperty("IntendedVersion") - javaPath = p.getProperty("JavaPath") - jvmArgs = p.getProperty("JvmArgs") - fullscreen = p.getProperty("LaunchMaximized") == "true" - maxMemory = p.getProperty("MaxMemAlloc")?.toIntOrNull() - minMemory = p.getProperty("MinMemAlloc")?.toIntOrNull() - height = p.getProperty("MinecraftWinHeight")?.toIntOrNull() - width = p.getProperty("MinecraftWinWidth")?.toIntOrNull() - overrideCommands = p.getProperty("OverrideCommands") == "true" - overrideConsole = p.getProperty("OverrideConsole") == "true" - overrideJavaArgs = p.getProperty("OverrideJavaArgs") == "true" - overrideJavaLocation = p.getProperty("OverrideJavaLocation") == "true" - overrideMemory = p.getProperty("OverrideMemory") == "true" - overrideWindow = p.getProperty("OverrideWindow") == "true" - permGen = p.getProperty("PermGen")?.toIntOrNull() - postExitCommand = p.getProperty("PostExitCommand") - preLaunchCommand = p.getProperty("PreLaunchCommand") - showConsole = p.getProperty("ShowConsole") == "true" - showConsoleOnError = p.getProperty("ShowConsoleOnError") == "true" - wrapperCommand = p.getProperty("WrapperCommand") - name = defaultName - notes = p.getProperty("notes") ?: "" - } -} - -fun readMMCModpackManifest(f: File): Modpack { - ZipFile(f).use { zipFile -> - val firstEntry = zipFile.entries.nextElement() - val name = firstEntry.name.substringBefore("/") - val entry = zipFile.getEntry("$name/instance.cfg") ?: throw IOException("`instance.cfg` not found, $f is not a valid MultiMC modpack.") - val cfg = InstanceConfiguration(name, zipFile.getInputStream(entry)) - return Modpack( - name = cfg.name, - version = "", - author = "", - gameVersion = cfg.gameVersion, - description = cfg.notes, - manifest = cfg - ) - } -} - -class MMCModpackInstallTask(private val dependencyManager: DefaultDependencyManager, private val zipFile: File, private val manifest: InstanceConfiguration, private val name: String): Task() { - private val repository = dependencyManager.repository - override val dependencies = mutableListOf() - override val dependents = mutableListOf() - - init { - check(!repository.hasVersion(name), { "Version $name already exists." }) - dependents += dependencyManager.gameBuilder().name(name).gameVersion(manifest.gameVersion).buildAsync() - - onDone += { event -> if (event.failed) repository.removeVersionFromDisk(name) } - } - - private val run = repository.getRunDirectory(name) - - override fun execute() { - var version = repository.readVersionJson(name)!! - zipFile.uncompressTo(run, subDirectory = "${manifest.name}/minecraft/", ignoreExistentFile = false, allowStoredEntriesWithDataDescriptor = true) - - ZipFile(zipFile).use { zip -> - for (entry in zip.entries) { - // ensure that this entry is in folder 'patches' and is a json file. - if (!entry.isDirectory && entry.name.startsWith("${manifest.name}/patches/") && entry.name.endsWith(".json")) { - val patch = GSON.fromJson(zip.getInputStream(entry).readFullyAsString())!! - val args = StringBuilder(version.minecraftArguments) - for (arg in patch.tweakers) - args.append(" --tweakClass ").append(arg) - version = version.copy( - libraries = merge(version.libraries, patch.libraries), - mainClass = patch.mainClass, - minecraftArguments = args.toString() - ) - } - } - } - - dependencies += VersionJSONSaveTask(repository, version) - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/CoupleTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/CoupleTask.kt deleted file mode 100644 index 6a70a3840..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/CoupleTask.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.task - -import org.jackhuang.hmcl.util.AutoTypingMap - -/** - * A task that combines two tasks and make sure [pred] runs before [succ]. - * - * @param pred the task that runs before [succ] - * @param succ a callback that returns the task runs after [pred], [succ] will be executed asynchronously. You can do something that relies on the result of [pred]. - * @param reliesOnDependents true if this task chain will be broken when task [pred] fails. - */ -internal class CoupleTask(pred: P, private val succ: (AutoTypingMap) -> Task?, override val reliesOnDependents: Boolean) : Task() { - override val hidden: Boolean = true - - override val dependents: Collection = listOf(pred) - override val dependencies: MutableCollection = mutableListOf() - - override fun execute() { - val task = this.succ(variables!!) - if (task != null) - dependencies += task - } -} - -/** - * @param b A runnable that decides what to do next, You can also do something here. - */ -infix fun T.then(b: (AutoTypingMap) -> Task?): Task = CoupleTask(this, b, true) - -/** - * @param b A runnable that decides what to do next, You can also do something here. - */ -infix fun T.with(b: (AutoTypingMap) -> Task?): Task = CoupleTask(this, b, false) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/FileDownloadTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/FileDownloadTask.kt deleted file mode 100644 index d330a6ca9..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/FileDownloadTask.kt +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.task - -import org.jackhuang.hmcl.event.EventManager -import org.jackhuang.hmcl.event.FailedEvent -import org.jackhuang.hmcl.util.* -import java.io.File -import java.io.IOException -import java.io.InputStream -import java.io.RandomAccessFile -import java.math.BigInteger -import java.net.Proxy -import java.net.URL -import java.util.logging.Level - -/** - * A task that can download a file online. - * - * @param url the URL of remote file. - * @param file the location that download to. - * @param hash the SHA-1 hash code of remote file, null if the hash is unknown or it is no need to check the hash code. - * @param retry the times for retrying if downloading fails. - * @param proxy the proxy. - * - * @author huangyuhui - */ -class FileDownloadTask @JvmOverloads constructor(val url: URL, val file: File, val hash: String? = null, val retry: Int = 5, val proxy: Proxy = Proxy.NO_PROXY): Task() { - override val scheduler: Scheduler = Scheduler.IO - - /** - * Once downloading fails, this event will be fired to gain the substitute URL. - */ - val onFailed = EventManager>() - - private var rFile: RandomAccessFile? = null - private var stream: InputStream? = null - - private fun closeFiles() { - rFile?.closeQuietly() - rFile = null - stream?.closeQuietly() - stream = null - } - - override fun execute() { - var currentURL = url - LOG.finer("Downloading: $currentURL, to: $file") - var exception: Exception? = null - for (repeat in 0 until retry) { - if (repeat > 0) { - val event = FailedEvent(this, repeat, currentURL) - onFailed(event) - if (currentURL != event.newResult) { - LOG.fine("Switch from: $currentURL to: ${event.newResult}") - currentURL = event.newResult - } - } - if (Thread.interrupted()) { - Thread.currentThread().interrupt() - break - } - - var temp: File? = null - - try { - updateProgress(0.0) - - val conn = currentURL.createConnection(proxy) - conn.connect() - - if (conn.responseCode / 100 != 2) - throw IOException("Server error, response code: ${conn.responseCode}") - - val contentLength = conn.contentLength - if (contentLength < 1) - throw IOException("The content length is invalid") - - if (!file.absoluteFile.parentFile.makeDirectory()) - throw IOException("Could not make directory: ${file.absoluteFile.parent}") - - temp = createTempFile("HMCLCore") - rFile = RandomAccessFile(temp, "rw") - - val digest = DigestUtils.sha1Digest - - stream = conn.inputStream - var lastDownloaded = 0 - var downloaded = 0 - var lastTime = System.currentTimeMillis() - val buf = ByteArray(4096) - while (true) { - if (Thread.interrupted()) { - Thread.currentThread().interrupt() - break - } - - val read = stream!!.read(buf) - if (read == -1) - break - - if (hash != null) - digest.update(buf, 0, read) - - rFile!!.write(buf, 0, read) - downloaded += read - - updateProgress(downloaded, contentLength) - val now = System.currentTimeMillis() - if (now - lastTime >= 1000L) { - updateMessage(((downloaded - lastDownloaded) / 1024).toString() + "KB/s") - lastDownloaded = downloaded - lastTime = now - } - } - - closeFiles() - - if (Thread.interrupted()) { - temp.delete() - Thread.currentThread().interrupt() - break - } else { - if (file.exists()) - file.delete() - if (!file.absoluteFile.parentFile.makeDirectory()) - throw IOException("Cannot make parent directory $file") - if (!temp.renameTo(file)) - throw IOException("Cannot move temp file to $file") - } - - check(downloaded == contentLength, { "Unexpected file size: $downloaded, expected: $contentLength" }) - - if (hash != null) { - val hashCode = String.format("%1$040x", BigInteger(1, digest.digest())) - check(hash.equals(hashCode, ignoreCase = true), { "Unexpected hash code: $hashCode, expected: $hash" }) - } - - return - } catch(e: Exception) { - temp?.delete() - exception = e - LOG.log(Level.WARNING, "Unable to download file $currentURL", e) - } finally { - closeFiles() - } - } - - if (exception != null) - throw exception - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt deleted file mode 100644 index 0e0a5ece8..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.task - -import org.jackhuang.hmcl.util.LOG -import org.jackhuang.hmcl.util.createConnection -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.net.Proxy -import java.net.URL -import java.nio.charset.Charset - -/** - * A task that can read the content of a remote text file. - * - * @param url the URL of remote text file. - * @param encoding the encoding/charset of the remote text file. - * @param retry the times for retrying if downloading fails. - * @param proxy the proxy. - * @param id the result variable id, see [Task.variables] - * - * @author huangyuhui - */ -class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY, override val id: String = ID): TaskResult() { - override val scheduler: Scheduler = Scheduler.IO - - override fun execute() { - var exception: IOException? = null - for (time in 0 until retry) { - if (time > 0) - LOG.warning("Unable to finish downloading $url, retrying time: $time") - try { - updateProgress(0.0) - val conn = url.createConnection(proxy) - val input = conn.inputStream - val baos = ByteArrayOutputStream() - val buf = ByteArray(4096) - val size = conn.contentLength - var read = 0 - while (true) { - val len = input.read(buf) - if (len == -1) - break - - baos.write(buf, 0, len) - read += len - updateProgress(read, size) - - if (Thread.currentThread().isInterrupted) - return - } - - if (size > 0 && size != read) { - throw IllegalStateException("Not completed! Readed: $read, Size: $size") - } - - result = baos.toString(encoding.name()) - return - } catch (e: IOException) { - exception = e - } - } - if (exception != null) - throw exception - } - - companion object { - /** - * The default task result ID. - */ - const val ID = "http_get" - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Scheduler.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Scheduler.kt deleted file mode 100644 index cd500a898..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Scheduler.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.task - -import java.util.concurrent.* -import java.util.concurrent.atomic.AtomicReference - -/** - * Determines how a task is executed. - * - * @see [Task.scheduler] - */ -interface Scheduler { - /** - * Schedules the given task. - * @return the future, null if future is not supported. - */ - fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() }) - - /** - * Schedules the given task. - * @return the future, null if future is not supported. - */ - fun schedule(block: Callable): Future<*>? - - private class SchedulerImpl(val executor: (Runnable) -> Unit) : Scheduler { - override fun schedule(block: Callable): Future<*>? { - val latch = CountDownLatch(1) - val wrapper = AtomicReference() - executor.invoke(Runnable { - try { - block.call() - } catch (e: Exception) { - wrapper.set(e) - } finally { - latch.countDown() - } - }) - return object : Future { - override fun get(timeout: Long, unit: TimeUnit) { - latch.await(timeout, unit) - val e = wrapper.get() - if (e != null) throw ExecutionException(e) - } - - override fun get() { - latch.await() - val e = wrapper.get() - if (e != null) throw ExecutionException(e) - } - - override fun isDone() = latch.count == 0L - override fun isCancelled() = false - override fun cancel(mayInterruptIfRunning: Boolean) = false - } - } - } - - private class SchedulerExecutorService(val executorService: ExecutorService) : Scheduler { - override fun schedule(block: Callable): Future<*> = executorService.submit(block) - } - - companion object Schedulers { - private val CACHED_EXECUTOR: ExecutorService by lazy { - ThreadPoolExecutor(0, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - SynchronousQueue()); - } - - private val IO_EXECUTOR: ExecutorService by lazy { - Executors.newFixedThreadPool(6) { r: Runnable -> - val thread: Thread = Executors.defaultThreadFactory().newThread(r) - thread.isDaemon = true - thread - } - } - - private val SINGLE_EXECUTOR: ExecutorService by lazy { - Executors.newSingleThreadExecutor { r: Runnable -> - val thread: Thread = Executors.defaultThreadFactory().newThread(r) - thread.isDaemon = true - thread - } - } - - val IMMEDIATE: Scheduler = object : Scheduler { - override fun schedule(block: Callable): Future<*>? { - block.call() - return null - } - } - - /** - * A scheduler for JavaFX UI operations. - */ - val JAVAFX: Scheduler = SchedulerImpl(javafx.application.Platform::runLater) - - /** - * A scheduler for Swing UI operations. - */ - val SWING: Scheduler = SchedulerImpl(javax.swing.SwingUtilities::invokeLater) - - /** - * A scheduler that always create new threads to execute tasks. - * For tasks that do not do heavy operations. - */ - val NEW_THREAD: Scheduler by lazy { - SchedulerExecutorService(CACHED_EXECUTOR) - } - - /** - * A scheduler that exclusively executes tasks that do I/O operations. - * The number tasks that do I/O operations in the meantime cannot be larger then 6. - */ - val IO: Scheduler by lazy { - SchedulerExecutorService(IO_EXECUTOR) - } - - /** - * A scheduler that exclusively executes tasks that do computations. - * The best way to do computations is an event queue. - */ - val COMPUTATION: Scheduler by lazy { - SchedulerExecutorService(SINGLE_EXECUTOR) - } - - /** - * The default scheduler for tasks to be executed. - * @see [Task.scheduler] - */ - val DEFAULT = NEW_THREAD - - /** - * Shut down all executor services to guarantee that the application can stop implicitly. - */ - fun shutdown() { - CACHED_EXECUTOR.shutdown() - IO_EXECUTOR.shutdown() - SINGLE_EXECUTOR.shutdown() - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt deleted file mode 100644 index 5d010fb74..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.task - -import javafx.beans.property.ReadOnlyDoubleProperty -import javafx.beans.property.ReadOnlyDoubleWrapper -import javafx.beans.property.ReadOnlyStringProperty -import javafx.beans.property.ReadOnlyStringWrapper -import org.jackhuang.hmcl.event.EventManager -import org.jackhuang.hmcl.util.AutoTypingMap -import org.jackhuang.hmcl.util.updateAsync -import java.util.concurrent.Callable -import java.util.concurrent.atomic.AtomicReference - -/** - * Disposable task. - * - * @see [TaskExecutor] - */ -abstract class Task { - /** - * True if not logging when executing this task. - */ - open val hidden: Boolean = false - - /** - * The scheduler that decides how this task runs. - */ - open val scheduler: Scheduler = Scheduler.DEFAULT - - /** - * True if requires all [dependents] finishing successfully. - * - * **Note** if this field is set false, you are not supposed to invoke [run] - * @defaultValue true - */ - open val reliesOnDependents: Boolean = true - - /** - * True if requires all [dependencies] finishing successfully. - * - * **Note** if this field is set false, you are not supposed to invoke [run] - * @defaultValue false - */ - open val reliesOnDependencies: Boolean = true - - open var title: String = this.javaClass.toString() - - var variables: AutoTypingMap? = null - - /** - * @see Thread.isInterrupted - * @throws InterruptedException if current thread is interrupted - */ - @Throws(Exception::class) - abstract fun execute() - - infix fun then(b: Task): Task = CoupleTask(this, { b }, true) - infix fun with(b: Task): Task = CoupleTask(this, { b }, false) - - /** - * The collection of sub-tasks that should execute **before** this task running. - */ - open val dependents: Collection = emptySet() - - /** - * The collection of sub-tasks that should execute **after** this task running. - */ - open val dependencies: Collection = emptySet() - - protected open val progressInterval = 1000L - private var lastTime = Long.MIN_VALUE - private val progressUpdate = AtomicReference() - private val progressPropertyImpl = ReadOnlyDoubleWrapper(this, "progress", 0.0) - val progressProperty: ReadOnlyDoubleProperty = progressPropertyImpl.readOnlyProperty - @JvmName("progressProperty") get - protected fun updateProgress(progress: Int, total: Int) = updateProgress(1.0 * progress / total) - protected fun updateProgress(progress: Double) { - val now = System.currentTimeMillis() - if (now - lastTime >= progressInterval) { - updateProgressImmediately(progress) - lastTime = now - } - } - - protected fun updateProgressImmediately(progress: Double) { - progressPropertyImpl.updateAsync(progress, progressUpdate) - } - - private val messageUpdate = AtomicReference() - private val messagePropertyImpl = ReadOnlyStringWrapper(this, "message", null) - val messageProperty: ReadOnlyStringProperty = messagePropertyImpl.readOnlyProperty - @JvmName("messageProperty") get - protected fun updateMessage(newMessage: String) = messagePropertyImpl.updateAsync(newMessage, messageUpdate) - - val onDone = EventManager() - - /** - * **Note** [reliesOnDependents] and [reliesOnDependencies] does not work here, which is always treated as true here. - */ - @Throws(Exception::class) - fun run() { - dependents.forEach(subTaskRunnable) - execute() - dependencies.forEach(subTaskRunnable) - onDone(TaskEvent(this, this, false)) - } - - private val subTaskRunnable = { task: Task -> - this.messagePropertyImpl.bind(task.messagePropertyImpl) - this.progressPropertyImpl.bind(task.progressPropertyImpl) - task.run() - this.messagePropertyImpl.unbind() - this.progressPropertyImpl.unbind() - } - - fun executor() = TaskExecutor(this) - fun executor(taskListener: TaskListener) = TaskExecutor(this).apply { this.taskListener = taskListener } - fun start() = executor().start() - fun test() = executor().test() - fun subscribe(subscriber: Task) = TaskExecutor(with(subscriber)).apply { start() } - - fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap) -> Unit) = subscribe(task(scheduler, closure)) - - override fun toString(): String { - return title - } -} - -fun task(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap) -> Unit): Task = SimpleTask(closure, scheduler) -fun taskResult(id: String, callable: Callable): TaskResult = TaskCallable(id, callable) -fun taskResult(id: String, callable: (AutoTypingMap) -> V): TaskResult = TaskCallable2(id, callable) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskEvent.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskEvent.kt deleted file mode 100644 index fa6d0cdbb..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskEvent.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.task - -import java.util.* - -class TaskEvent(source: Any, val task: Task, val failed: Boolean) : EventObject(source) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt deleted file mode 100644 index 0979f9dad..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.task - -import org.jackhuang.hmcl.util.AutoTypingMap -import org.jackhuang.hmcl.util.LOG -import java.util.concurrent.Callable -import java.util.concurrent.ConcurrentLinkedQueue -import java.util.concurrent.CountDownLatch -import java.util.concurrent.Future -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicInteger -import java.util.logging.Level - -class TaskExecutor(private val task: Task) { - var taskListener: TaskListener? = null - - var canceled = false - private set - val totTask = AtomicInteger(0) - val variables = AutoTypingMap(mutableMapOf()) - var lastException: Exception? = null - private set - private val workerQueue = ConcurrentLinkedQueue>() - - /** - * Start the subscription and run all registered tasks asynchronously. - */ - fun start() { - workerQueue.add(Scheduler.NEW_THREAD.schedule { - if (!executeTasks(listOf(task))) - taskListener?.onTerminate() - }) - } - - @Throws(InterruptedException::class) - fun test(): Boolean { - var flag = true - val future = Scheduler.NEW_THREAD.schedule { - if (!executeTasks(listOf(task))) { - taskListener?.onTerminate() - flag = false - } - } - workerQueue.add(future) - future!!.get() - return flag - } - - /** - * Cancel the subscription ant interrupt all tasks. - */ - fun cancel() { - canceled = true - - while (!workerQueue.isEmpty()) - workerQueue.poll()?.cancel(true) - } - - private fun executeTasks(tasks: Collection): Boolean { - if (tasks.isEmpty()) - return true - - totTask.addAndGet(tasks.size) - val success = AtomicBoolean(true) - val counter = CountDownLatch(tasks.size) - for (task in tasks) { - if (canceled) - return false - val invoker = Invoker(task, counter, success) - val future = task.scheduler.schedule(invoker) - if (future != null) - workerQueue.add(future) - } - if (canceled) - return false - try { - counter.await() - return success.get() && !canceled - } catch (e: InterruptedException) { - Thread.currentThread().interrupt() - // Once interrupted, we are aborting the subscription. - // and operations fail. - return false - } - } - - private fun executeTask(t: Task): Boolean { - if (canceled) - return false - - if (!t.hidden) - LOG.fine("Executing task: ${t.title}") - taskListener?.onReady(t) - val doDependentsSucceeded = executeTasks(t.dependents) - - var flag = false - try { - if (!doDependentsSucceeded && t.reliesOnDependents || canceled) - throw SilentException() - - t.variables = variables - t.execute() - if (t is TaskResult<*>) - variables[t.id] = t.result - - if (!executeTasks(t.dependencies) && t.reliesOnDependencies) - throw IllegalStateException("Subtasks failed for ${t.title}") - - flag = true - if (!t.hidden) - LOG.finer("Task finished: ${t.title}") - - if (!t.hidden) { - t.onDone(TaskEvent(source = this, task = t, failed = false)) - taskListener?.onFinished(t) - } - } catch (e: InterruptedException) { - if (!t.hidden) { - lastException = e - LOG.log(Level.FINE, "Task aborted: ${t.title}", e) - t.onDone(TaskEvent(source = this, task = t, failed = true)) - taskListener?.onFailed(t, e) - } - } catch (e: SilentException) { - // nothing here - } catch (e: Exception) { - if (!t.hidden) { - lastException = e - LOG.log(Level.SEVERE, "Task failed: ${t.title}", e) - t.onDone(TaskEvent(source = this, task = t, failed = true)) - taskListener?.onFailed(t, e) - } - } finally { - t.variables = null - } - return flag - } - - private inner class Invoker(private val task: Task, private val latch: CountDownLatch, private val boolean: AtomicBoolean): Callable { - override fun call() { - try { - Thread.currentThread().name = task.title - if (!executeTask(task)) - boolean.set(false) - } finally { - latch.countDown() - } - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskListener.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskListener.kt deleted file mode 100644 index d55a53b79..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskListener.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.task - -import java.util.* - -interface TaskListener : EventListener { - fun onReady(task: Task) {} - fun onFinished(task: Task) {} - fun onFailed(task: Task, throwable: Throwable) {} - fun onTerminate() {} -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt deleted file mode 100644 index b61aaa052..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import org.apache.commons.compress.archivers.zip.ZipArchiveEntry -import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream -import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream -import org.apache.commons.compress.archivers.zip.ZipFile -import java.io.File -import java.io.IOException - -/** - * Compress the given directory to a zip file. - * - * @param src the source directory or a file. - * @param destZip the location of dest zip file. - * @param pathNameCallback callback(pathName, isDirectory) returns your modified pathName - * @throws IOException - */ -@JvmOverloads -@Throws(IOException::class) -fun File.zipTo(destZip: File, pathNameCallback: ((String, Boolean) -> String?)? = null) { - ZipArchiveOutputStream(destZip.outputStream()).use { zos -> - val basePath: String - if (this.isDirectory) - basePath = this.path - else - //直接压缩单个文件时,取父目录 - basePath = this.parent - zipFile(this, basePath, zos, pathNameCallback) - zos.closeArchiveEntry() - } -} - -/** - * Zip file. - * - * @param src source directory to be compressed. - * @param basePath the file directory to be compressed, if [src] is a file, this is the parent directory of [src] - * @param zos the [ZipOutputStream] of dest zip file. - * @param pathNameCallback callback(pathName, isDirectory) returns your modified pathName, null if you dont want this file zipped - * @throws IOException - */ -@Throws(IOException::class) -private fun zipFile(src: File, - basePath: String, - zos: ZipArchiveOutputStream, - pathNameCallback: ((String, Boolean) -> String?)?) { - val files: Array - if (src.isDirectory) - files = src.listFiles() ?: emptyArray() - else { - files = arrayOf(src) - } - var pathName: String? //存相对路径(相对于待压缩的根目录) - val buf = ByteArray(1024) - for (file in files) - if (file.isDirectory) { - pathName = file.path.substring(basePath.length + 1) + "/" - if (pathNameCallback != null) - pathName = pathNameCallback.invoke(pathName, true) - if (pathName == null) - continue - zos.putArchiveEntry(ZipArchiveEntry(pathName)) - zipFile(file, basePath, zos, pathNameCallback) - } else { - pathName = file.path.substring(basePath.length + 1) - if (pathNameCallback != null) - pathName = pathNameCallback.invoke(pathName, true) - if (pathName == null) - continue - file.inputStream().use { inputStream -> - zos.putArchiveEntry(ZipArchiveEntry(pathName)) - inputStream.copyTo(zos, buf) - } - } -} - -/** - * Decompress the given zip file to a directory. - * - * @param dest the dest directory. - * @param subDirectory the subdirectory of the zip file to be decompressed. - * @param callback will be called for every entry in the zip file, returns false if you dont want this file being uncompressed. - * @param ignoreExistentFile true if skip all existent files. - * @param allowStoredEntriesWithDataDescriptor whether the zip stream will try to read STORED entries that use a data descriptor - * @throws IOException - */ -@JvmOverloads -@Throws(IOException::class) -fun File.uncompressTo(dest: File, subDirectory: String = "", callback: ((String) -> Boolean)? = null, ignoreExistentFile: Boolean = true, allowStoredEntriesWithDataDescriptor: Boolean = false) { - val buf = ByteArray(1024) - dest.mkdirs() - ZipArchiveInputStream(this.inputStream(), null, true, allowStoredEntriesWithDataDescriptor).use { zipFile -> - if (this.exists()) { - val strPath = dest.absolutePath - while (true) { - val zipEnt = zipFile.nextEntry ?: break - var strtemp: String - var gbkPath: String - gbkPath = zipEnt.name - if (!gbkPath.startsWith(subDirectory)) - continue - gbkPath = gbkPath.substring(subDirectory.length) - if (gbkPath.startsWith("/") || gbkPath.startsWith("\\")) gbkPath = gbkPath.substring(1) - strtemp = strPath + File.separator + gbkPath - - if (callback != null) - if (!callback.invoke(gbkPath)) - continue - - if (zipEnt.isDirectory) { - val dir = File(strtemp) - dir.mkdirs() - } else { - //建目录 - val strsubdir = gbkPath - for (i in 0 until strsubdir.length) - if (strsubdir.substring(i, i + 1).equals("/", ignoreCase = true)) { - val temp = strPath + File.separator + strsubdir.substring(0, i) - val subdir = File(temp) - if (!subdir.exists()) - subdir.mkdir() - } - if (ignoreExistentFile && File(strtemp).exists()) - continue - File(strtemp).outputStream().use({ fos -> - zipFile.copyTo(fos, buf) - }) - } - } - } - } -} - -/** - * Read the text content of a file in zip. - * - * @param name the location of the text in zip file, something like A/B/C/D.txt - * @throws IOException if the file is not a valid zip file. - * @return the content of given file. - */ -@Throws(IOException::class) -fun File.readTextZipEntry(name: String): String { - ZipFile(this).use { zipFile -> - val entry = zipFile.getEntry(name) ?: throw IOException("`$name` not found.") - return zipFile.getInputStream(entry).readFullyAsString() - } -} - -/** - * Read the text content of a file in zip. - * - * @param location the location of the text in zip file, something like A/B/C/D.txt - * @return the content of given file. - */ -fun File.readTextZipEntryQuietly(location: String) = - try { - readTextZipEntry(location) - } catch (e: IOException) { - null - } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Constants.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Constants.kt deleted file mode 100644 index 24d75e1ef..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Constants.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -@file:JvmName("Constants") -package org.jackhuang.hmcl.util - -import javafx.application.Platform -import java.awt.EventQueue -import java.nio.charset.Charset - -val DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/" -val DEFAULT_VERSION_DOWNLOAD_URL = "http://s3.amazonaws.com/Minecraft.Download/versions/" -val DEFAULT_INDEX_URL = "http://s3.amazonaws.com/Minecraft.Download/indexes/" - -val SWING_UI_THREAD_SCHEDULER = { runnable: () -> Unit -> - if (EventQueue.isDispatchThread()) - runnable() - else - EventQueue.invokeLater(runnable) -} - -val JAVAFX_UI_THREAD_SCHEDULER = { runnable: () -> Unit -> - if (Platform.isFxApplicationThread()) - runnable() - else - Platform.runLater(runnable) -} - -val UI_THREAD_SCHEDULER: (() -> Unit) -> Unit = { } - -val DEFAULT_ENCODING = "UTF-8" - -val DEFAULT_CHARSET = Charsets.UTF_8 -val SYSTEM_CHARSET: Charset by lazy { charset(OS.ENCODING) } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/DigestUtils.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/DigestUtils.kt deleted file mode 100644 index a23c89b3f..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/DigestUtils.kt +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.io.IOException -import java.io.InputStream -import java.security.MessageDigest -import java.security.NoSuchAlgorithmException - -/** - * @author huangyuhui - */ -object DigestUtils { - - private val STREAM_BUFFER_LENGTH = 1024 - - @Throws(IOException::class) - private fun digest(digest: MessageDigest, data: InputStream): ByteArray { - return updateDigest(digest, data).digest() - } - - fun getDigest(algorithm: String): MessageDigest { - try { - return MessageDigest.getInstance(algorithm) - } catch (e: NoSuchAlgorithmException) { - throw IllegalArgumentException(e) - } - - } - - val md2Digest: MessageDigest - get() = getDigest("MD2") - - val md5Digest: MessageDigest - get() = getDigest("MD5") - - val sha1Digest: MessageDigest - get() = getDigest("SHA-1") - - val sha256Digest: MessageDigest - get() = getDigest("SHA-256") - - val sha384Digest: MessageDigest - get() = getDigest("SHA-384") - - val sha512Digest: MessageDigest - get() = getDigest("SHA-512") - - fun md2(data: ByteArray): ByteArray { - return md2Digest.digest(data) - } - - @Throws(IOException::class) - fun md2(data: InputStream): ByteArray { - return digest(md2Digest, data) - } - - fun md2(data: String): ByteArray { - return md2(data.toByteArray(Charsets.UTF_8)) - } - - fun md2Hex(data: ByteArray): String { - return Hex.encodeHexString(md2(data)) - } - - @Throws(IOException::class) - fun md2Hex(data: InputStream): String { - return Hex.encodeHexString(md2(data)) - } - - fun md2Hex(data: String): String { - return Hex.encodeHexString(md2(data)) - } - - fun md5(data: ByteArray): ByteArray { - return md5Digest.digest(data) - } - - @Throws(IOException::class) - fun md5(data: InputStream): ByteArray { - return digest(md5Digest, data) - } - - fun md5(data: String): ByteArray { - return md5(data.toByteArray(Charsets.UTF_8)) - } - - fun md5Hex(data: ByteArray): String { - return Hex.encodeHexString(md5(data)) - } - - @Throws(IOException::class) - fun md5Hex(data: InputStream): String { - return Hex.encodeHexString(md5(data)) - } - - fun md5Hex(data: String): String { - return Hex.encodeHexString(md5(data)) - } - - fun sha1(data: ByteArray): ByteArray { - return sha1Digest.digest(data) - } - - @Throws(IOException::class) - fun sha1(data: InputStream): ByteArray { - return digest(sha1Digest, data) - } - - fun sha1(data: String): ByteArray { - return sha1(data.toByteArray(Charsets.UTF_8)) - } - - fun sha1Hex(data: ByteArray): String { - return Hex.encodeHexString(sha1(data)) - } - - @Throws(IOException::class) - fun sha1Hex(data: InputStream): String { - return Hex.encodeHexString(sha1(data)) - } - - fun sha1Hex(data: String): String { - return Hex.encodeHexString(sha1(data)) - } - - fun sha256(data: ByteArray): ByteArray { - return sha256Digest.digest(data) - } - - @Throws(IOException::class) - fun sha256(data: InputStream): ByteArray { - return digest(sha256Digest, data) - } - - fun sha256(data: String): ByteArray { - return sha256(data.toByteArray(Charsets.UTF_8)) - } - - fun sha256Hex(data: ByteArray): String { - return Hex.encodeHexString(sha256(data)) - } - - @Throws(IOException::class) - fun sha256Hex(data: InputStream): String { - return Hex.encodeHexString(sha256(data)) - } - - fun sha256Hex(data: String): String { - return Hex.encodeHexString(sha256(data)) - } - - fun sha384(data: ByteArray): ByteArray { - return sha384Digest.digest(data) - } - - @Throws(IOException::class) - fun sha384(data: InputStream): ByteArray { - return digest(sha384Digest, data) - } - - fun sha384(data: String): ByteArray { - return sha384(data.toByteArray(Charsets.UTF_8)) - } - - fun sha384Hex(data: ByteArray): String { - return Hex.encodeHexString(sha384(data)) - } - - @Throws(IOException::class) - fun sha384Hex(data: InputStream): String { - return Hex.encodeHexString(sha384(data)) - } - - fun sha384Hex(data: String): String { - return Hex.encodeHexString(sha384(data)) - } - - fun sha512(data: ByteArray): ByteArray { - return sha512Digest.digest(data) - } - - @Throws(IOException::class) - fun sha512(data: InputStream): ByteArray { - return digest(sha512Digest, data) - } - - fun sha512(data: String): ByteArray { - return sha512(data.toByteArray(Charsets.UTF_8)) - } - - fun sha512Hex(data: ByteArray): String { - return Hex.encodeHexString(sha512(data)) - } - - @Throws(IOException::class) - fun sha512Hex(data: InputStream): String { - return Hex.encodeHexString(sha512(data)) - } - - fun sha512Hex(data: String): String { - return Hex.encodeHexString(sha512(data)) - } - - fun updateDigest(messageDigest: MessageDigest, valueToDigest: ByteArray): MessageDigest { - messageDigest.update(valueToDigest) - return messageDigest - } - - @Throws(IOException::class) - fun updateDigest(digest: MessageDigest, data: InputStream): MessageDigest { - val buffer = ByteArray(STREAM_BUFFER_LENGTH) - var read = data.read(buffer, 0, STREAM_BUFFER_LENGTH) - - while (read > -1) { - digest.update(buffer, 0, read) - read = data.read(buffer, 0, STREAM_BUFFER_LENGTH) - } - - return digest - } - - fun updateDigest(messageDigest: MessageDigest, valueToDigest: String): MessageDigest { - messageDigest.update(valueToDigest.toByteArray(Charsets.UTF_8)) - return messageDigest - } -} diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/FileUtils.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/FileUtils.kt deleted file mode 100644 index a3f43a6b5..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/FileUtils.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.io.File - -fun File.makeDirectory(): Boolean = isDirectory || mkdirs() - -fun File.makeFile(): Boolean { - if (!absoluteFile.parentFile.makeDirectory()) - return false - if (!exists() && !createNewFile()) - return false - return true -} - -fun File.isSymlink(): Boolean { - if (File.separatorChar == '\\') - return false - val fileInCanonicalDir: File = - if (parent == null) this - else File(parentFile.canonicalFile, name) - return fileInCanonicalDir.canonicalFile != fileInCanonicalDir.absoluteFile -} - -fun File.listFilesByExtension(ext: String): List { - val list = mutableListOf() - this.listFiles()?.filter { it.extension == ext }?.forEach { list.add(it) } - return list -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Gson.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Gson.kt deleted file mode 100644 index a4b57e7eb..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Gson.kt +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import com.google.gson.* -import com.google.gson.reflect.TypeToken -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken -import com.google.gson.stream.JsonWriter -import org.jackhuang.hmcl.game.Argument -import org.jackhuang.hmcl.game.Library -import org.jackhuang.hmcl.game.RuledArgument -import java.io.File -import java.io.IOException -import java.lang.reflect.Type -import java.text.DateFormat -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.* -import java.util.UUID - -val GSON: Gson = GsonBuilder() - .enableComplexMapKeySerialization() - .setPrettyPrinting() - .registerTypeAdapter(Library::class.java, Library) - .registerTypeAdapter(Argument::class.java, Argument) - .registerTypeAdapter(RuledArgument::class.java, RuledArgument) - .registerTypeAdapter(Date::class.java, DateTypeAdapter) - .registerTypeAdapter(UUID::class.java, UUIDTypeAdapter) - .registerTypeAdapter(Platform::class.java, Platform) - .registerTypeAdapter(File::class.java, FileTypeAdapter) - .registerTypeAdapterFactory(ValidationTypeAdapterFactory) - .registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory) - .create() - -inline fun typeOf(): Type = object : TypeToken() {}.type - -inline fun Gson.fromJson(json: String): T? = fromJson(json, T::class.java) - -inline fun Gson.fromJsonQuietly(json: String): T? { - try { - return fromJson(json) - } catch (json: JsonParseException) { - return null - } -} - -/** - * Check if the json object's fields automatically filled by Gson are in right format. - */ -interface Validation { - /** - * 1. Check some non-null fields and; - * 2. Check strings and; - * 3. Check generic type of lists and maps are correct. - * - * Will be called immediately after initialization. - * Throw an exception when values are malformed. - * @throws JsonParseException if fields are filled in wrong format or wrong type. - */ - fun validate() -} - -object ValidationTypeAdapterFactory : TypeAdapterFactory { - override fun create(gson: Gson, type: TypeToken?): TypeAdapter { - val delegate = gson.getDelegateAdapter(this, type) - return object : TypeAdapter() { - override fun write(out: JsonWriter?, value: T?) { - if (value is Validation) - value.validate() - delegate.write(out, value) - } - - override fun read(reader: JsonReader?): T? { - val value = delegate.read(reader) - if (value is Validation) - value.validate() - return value - } - } - } -} - -object LowerCaseEnumTypeAdapterFactory : TypeAdapterFactory { - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - val rawType = type.rawType - if (!rawType.isEnum) { - return null - } - val lowercaseToConstant = HashMap() - for (constant in rawType.enumConstants) { - @Suppress("UNCHECKED_CAST") - lowercaseToConstant.put(toLowercase(constant!!), constant as T) - } - return object : TypeAdapter() { - @Throws(IOException::class) - override fun write(out: JsonWriter, value: T?) { - if (value == null) { - out.nullValue() - } else { - out.value(toLowercase(value)) - } - } - - @Throws(IOException::class) - override fun read(reader: JsonReader): T? { - if (reader.peek() == JsonToken.NULL) { - reader.nextNull() - return null - } - return lowercaseToConstant[reader.nextString().toLowerCase()] - } - } - } - - private fun toLowercase(o: Any): String { - return o.toString().toLowerCase(Locale.US) - } -} - -object UUIDTypeAdapter : TypeAdapter() { - override fun read(reader: JsonReader): UUID { - return fromString(reader.nextString()) - } - - override fun write(writer: JsonWriter, value: UUID?) { - writer.value(if (value == null) null else fromUUID(value)) - } - - fun fromUUID(value: UUID): String { - return value.toString().replace("-", "") - } - - fun fromString(input: String): UUID { - return UUID.fromString(input.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})".toRegex(), "$1-$2-$3-$4-$5")) - } - -} - -object DateTypeAdapter : JsonSerializer, JsonDeserializer { - private val enUsFormat: DateFormat = DateFormat.getDateTimeInstance(2, 2, Locale.US) - private val iso8601Format: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Date { - if (json !is JsonPrimitive) { - throw JsonParseException("The date should be a string value") - } else { - val date = this.deserializeToDate(json.asString) - if (typeOfT === Date::class.java) { - return date - } else { - throw IllegalArgumentException(this.javaClass.toString() + " cannot deserialize to " + typeOfT) - } - } - } - - override fun serialize(src: Date, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - synchronized(enUsFormat) { - return JsonPrimitive(this.serializeToString(src)) - } - } - - fun deserializeToDate(string: String): Date { - synchronized(enUsFormat) { - try { - return enUsFormat.parse(string) - } catch (ex1: ParseException) { - try { - return iso8601Format.parse(string) - } catch (ex2: ParseException) { - try { - var cleaned = string.replace("Z", "+00:00") - cleaned = cleaned.substring(0, 22) + cleaned.substring(23) - return iso8601Format.parse(cleaned) - } catch (e: Exception) { - throw JsonSyntaxException("Invalid date: " + string, e) - } - } - } - } - } - - fun serializeToString(date: Date): String { - synchronized(this.enUsFormat) { - val result = this.iso8601Format.format(date) - return result.substring(0, 22) + ":" + result.substring(22) - } - } -} - -object FileTypeAdapter : JsonSerializer, JsonDeserializer { - override fun serialize(src: File?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { - if (src == null) return JsonNull.INSTANCE - else return JsonPrimitive(src.path) - } - - override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): File? { - if (json == null) return null - else return File(json.asString) - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Hex.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Hex.kt deleted file mode 100644 index 4448ea6c0..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Hex.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.nio.charset.Charset - -class Hex @JvmOverloads constructor(val charset: Charset = DEFAULT_CHARSET) { - - @Throws(Exception::class) - fun decode(array: ByteArray): ByteArray { - return decodeHex(String(array, charset).toCharArray()) - } - - @Throws(Exception::class) - fun decode(`object`: Any): Any { - try { - val charArray = (`object` as? String)?.toCharArray() ?: `object` as CharArray - return decodeHex(charArray) - } catch (e: ClassCastException) { - throw Exception(e.message, e) - } - - } - - fun encode(array: ByteArray): ByteArray { - return encodeHexString(array).toByteArray(charset) - } - - @Throws(Exception::class) - fun encode(`object`: Any): Any { - try { - val byteArray = (`object` as? String)?.toByteArray(charset) ?: `object` as ByteArray - - return encodeHex(byteArray) - } catch (e: ClassCastException) { - throw Exception(e.message, e) - } - - } - - val charsetName: String - get() = this.charset.name() - - override fun toString(): String { - return super.toString() + "[charsetName=$charset]" - } - - companion object { - - private val DIGITS_LOWER = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') - - private val DIGITS_UPPER = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F') - - @Throws(Exception::class) - fun decodeHex(data: CharArray): ByteArray { - val len = data.size - - if (len and 0x1 != 0) - throw Exception("Odd number of characters.") - - val out = ByteArray(len shr 1) - - var i = 0 - var j = 0 - while (j < len) { - var f = toDigit(data[j], j) shl 4 - j++ - f = f or toDigit(data[j], j) - j++ - out[i] = (f and 0xFF).toByte() - i++ - } - - return out - } - - @JvmOverloads fun encodeHex(data: ByteArray, toLowerCase: Boolean = true): CharArray { - return encodeHex(data, if (toLowerCase) DIGITS_LOWER else DIGITS_UPPER) - } - - protected fun encodeHex(data: ByteArray, toDigits: CharArray): CharArray { - val l = data.size - val out = CharArray(l shl 1) - - var i = 0 - var j = 0 - while (i < l) { - out[j++] = toDigits[(0xF0 and data[i].toInt()).ushr(4)] - out[j++] = toDigits[0xF and data[i].toInt()] - i++ - } - return out - } - - fun encodeHexString(data: ByteArray): String { - return String(encodeHex(data)) - } - - protected fun toDigit(ch: Char, index: Int): Int { - val digit = Character.digit(ch, 16) - if (digit == -1) - throw IllegalArgumentException("Illegal hexadecimal character $ch at index $index") - return digit - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/IOUtils.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/IOUtils.kt deleted file mode 100644 index 0f5f0d9ac..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/IOUtils.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.io.* -import java.nio.charset.Charset - -const val MAX_BUFFER_SIZE = 4096 - -fun Closeable.closeQuietly() { - try { - this.close() - } catch (ex: IOException) {} -} - -fun InputStream.readFully(): ByteArrayOutputStream { - try { - return ByteArrayOutputStream().apply { copyTo(this) } - } finally { - this.closeQuietly() - } -} - -fun InputStream.readFullyAsByteArray(): ByteArray = - readFully().toByteArray() - -fun InputStream.readFullyAsString(): String = - readFully().toString() - -fun InputStream.readFullyAsString(charset: Charset): String = - readFully().toString(charset.name()) - -fun InputStream.copyTo(dest: OutputStream, buf: ByteArray) { - while (true) { - val len = read(buf) - if (len == -1) - break - dest.write(buf, 0, len) - } -} - -fun InputStream.copyToAndClose(dest: OutputStream) { - this.use { input -> - dest.use { output -> - input.copyTo(output) - } - } -} - -/** - * @param cmd the command line - * @return the final command line - */ -fun makeCommand(cmd: List): String { - val cmdbuf = StringBuilder(120) - for (i in cmd.indices) { - if (i > 0) - cmdbuf.append(' ') - val s = cmd[i] - if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) - if (s[0] != '"') { - cmdbuf.append('"') - cmdbuf.append(s) - if (s.endsWith("\\")) - cmdbuf.append("\\") - cmdbuf.append('"') - } else if (s.endsWith("\"")) - // The argument has already been quoted. - cmdbuf.append(s) - else - // Unmatched quote for the argument. - throw IllegalArgumentException() - else - cmdbuf.append(s) - } - - return cmdbuf.toString() -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ImmediateProperties.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ImmediateProperties.kt deleted file mode 100644 index ebd32c914..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ImmediateProperties.kt +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import javafx.beans.property.* -import javafx.beans.value.ChangeListener -import javafx.beans.value.ObservableValue - -/** - * Any operation of properties should run on JavaFX thread. - */ -open class ImmediateStringProperty(bean: Any, name: String, initialValue: String): SimpleStringProperty(bean, name, initialValue) { - - override fun set(newValue: String) { - super.get() - super.set(newValue) - } - - override fun bind(newObservable: ObservableValue) { - super.get() - super.bind(newObservable) - } - - override fun unbind() { - super.get() - super.unbind() - } - - private var myListener: (String) -> Unit = {} - private val changeListener = ChangeListener { _, _, newValue -> - myListener(newValue) - } - - fun setChangedListener(listener: (String) -> Unit) { - myListener = listener - } - - init { - addListener(changeListener) - } -} - -open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boolean): SimpleBooleanProperty(bean, name, initialValue) { - - override fun set(newValue: Boolean) { - super.get() - super.set(newValue) - } - - override fun bind(rawObservable: ObservableValue?) { - super.get() - super.bind(rawObservable) - } - - override fun unbind() { - super.get() - super.unbind() - } - - private var myListener: (Boolean) -> Unit = {} - private val changeListener = ChangeListener { _, _, newValue -> - myListener(newValue) - } - - fun setChangedListener(listener: (Boolean) -> Unit) { - myListener = listener - } - - init { - addListener(changeListener) - } -} - -open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int): SimpleIntegerProperty(bean, name, initialValue) { - - override fun set(newValue: Int) { - super.get() - super.set(newValue) - } - - override fun bind(rawObservable: ObservableValue) { - super.get() - super.bind(rawObservable) - } - - override fun unbind() { - super.get() - super.unbind() - } - - private var myListener: (Int) -> Unit = {} - private val changeListener = ChangeListener { _, _, newValue -> - myListener(newValue.toInt()) - } - - fun setChangedListener(listener: (Int) -> Unit) { - myListener = listener - } - - init { - addListener(changeListener) - } -} - -open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double): SimpleDoubleProperty(bean, name, initialValue) { - - override fun set(newValue: Double) { - super.get() - super.set(newValue) - } - - override fun bind(rawObservable: ObservableValue) { - super.get() - super.bind(rawObservable) - } - - override fun unbind() { - super.get() - super.unbind() - } - - private var myListener: (Double) -> Unit = {} - private val changeListener = ChangeListener { _, _, newValue -> - myListener(newValue.toDouble()) - } - - fun setChangedListener(listener: (Double) -> Unit) { - myListener = listener - } - - init { - addListener(changeListener) - } -} - -open class ImmediateObjectProperty(bean: Any, name: String, initialValue: T): SimpleObjectProperty(bean, name, initialValue) { - - override fun set(newValue: T) { - super.get() - super.set(newValue) - } - - override fun bind(rawObservable: ObservableValue) { - super.get() - super.bind(rawObservable) - } - - override fun unbind() { - super.get() - super.unbind() - } - - private var myListener: (T) -> Unit = {} - private val changeListener = ChangeListener { _, _, newValue -> - myListener(newValue) - } - - fun setChangedListener(listener: (T) -> Unit) { - myListener = listener - listener(get()) - } - - init { - addListener(changeListener) - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/JavaVersion.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/JavaVersion.kt deleted file mode 100644 index bfb5f9a2e..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/JavaVersion.kt +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import com.google.gson.annotations.SerializedName -import org.jackhuang.hmcl.game.Version -import java.io.File -import java.io.IOException -import java.io.Serializable -import java.util.* -import java.util.concurrent.CountDownLatch -import java.util.regex.Pattern - -/** - * Represents a Java installation. - */ -data class JavaVersion internal constructor( - @SerializedName("location") - val binary: File, - val longVersion: String, - val platform: Platform) : Serializable -{ - /** - * The major version of Java installation. - * - * @see JAVA_X - * @see JAVA_9 - * @see JAVA_8 - * @see JAVA_7 - * @see JAVA_6 - * @see JAVA_5 - * @see UNKNOWN - */ - val version = parseVersion(longVersion) - - companion object { - private val regex = Pattern.compile("java version \"(?(.*?))\"") - - val UNKNOWN: Int = -1 - val JAVA_5: Int = 50 - val JAVA_6: Int = 60 - val JAVA_7: Int = 70 - val JAVA_8: Int = 80 - val JAVA_9: Int = 90 - val JAVA_X: Int = 100 - - private fun parseVersion(version: String): Int { - with(version) { - if (startsWith("10") || startsWith("X")) return JAVA_X - else if (contains("1.9.") || startsWith("9")) return JAVA_9 - else if (contains("1.8.")) return JAVA_8 - else if (contains("1.7.")) return JAVA_7 - else if (contains("1.6.")) return JAVA_6 - else if (contains("1.5.")) return JAVA_5 - else return UNKNOWN - } - } - - @Throws(IOException::class) - fun fromExecutable(file: File): JavaVersion { - var actualFile = file - var platform = Platform.BIT_32 - var version: String? = null - if (actualFile.nameWithoutExtension == "javaw") // javaw will not output version information - actualFile = actualFile.absoluteFile.parentFile.resolve("java") - try { - val process = ProcessBuilder(actualFile.absolutePath, "-version").start() - process.waitFor() - process.errorStream.bufferedReader().forEachLine { line -> - val m = regex.matcher(line) - if (m.find()) - version = m.group("version") - if (line.contains("64-Bit")) - platform = Platform.BIT_64 - } - } catch (e: InterruptedException) { - Thread.currentThread().interrupt() - throw IOException("Java process is interrupted", e) - } - val thisVersion = version ?: throw IOException("Java version not matched") - val parsedVersion = parseVersion(thisVersion) - if (parsedVersion == UNKNOWN) - throw IOException("Java version '$thisVersion' can not be recognized") - return JavaVersion(file, thisVersion, platform) - } - - private fun fromExecutable(file: File, version: String) = - JavaVersion ( - binary = file, - longVersion = version, - platform = Platform.UNKNOWN - ) - - @Throws(IOException::class) - fun fromJavaHome(home: File): JavaVersion { - return fromExecutable(getJavaFile(home)) - } - - private fun fromJavaHome(home: File, version: String): JavaVersion { - return fromExecutable(getJavaFile(home), version) - } - - private fun getJavaFile(home: File): File { - val path = home.resolve("bin") - val javaw = path.resolve("javaw.exe") - if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile) - return javaw - else - return path.resolve("java") - } - - private val currentJava: JavaVersion by lazy { - JavaVersion( - binary = getJavaFile(File(System.getProperty("java.home"))), - longVersion = System.getProperty("java.version"), - platform = Platform.PLATFORM) - } - fun fromCurrentEnvironment() = currentJava - - private var javas: Map? = null - private val await = CountDownLatch(1) - - /** - * This method will block until [initialize] succeeds. - */ - fun getJREs(): Map { - if (javas != null) return javas!! - await.await() - return javas!! - } - - @Synchronized - fun initialize() { - if (javas != null) - throw IllegalStateException("JavaVersions have already been initialized.") - val temp = mutableMapOf() - temp += currentJava.longVersion to currentJava - (when (OS.CURRENT_OS) { - OS.WINDOWS -> queryWindows() - OS.OSX -> queryMacintosh() - else -> emptyList() /* Cannot detect Java in linux. */ - }).forEach { javaVersion -> - temp.put(javaVersion.longVersion, javaVersion) - } - javas = temp - await.countDown() - } - - private fun queryMacintosh() = LinkedList().apply { - val currentJRE = File("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home") - if (currentJRE.exists()) - this += fromJavaHome(currentJRE) - File("/Library/Java/JavaVirtualMachines/").listFiles()?.forEach { file -> - this += fromJavaHome(file.resolve("Contents/Home")) - } - } - - private fun queryWindows() = LinkedList().apply { - ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\") } - ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\") } - ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\") } - ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\") } - } - - private fun queryRegisterKey(location: String) = LinkedList().apply { - querySubFolders(location).forEach { java -> - val home = queryRegisterValue(java, "JavaHome") - if (home != null) - this += fromJavaHome(File(home)) - } - } - - private fun querySubFolders(location: String) = LinkedList().apply { - val cmd = arrayOf("cmd", "/c", "reg", "query", location) - val process = Runtime.getRuntime().exec(cmd) - process.waitFor() - process.inputStream.bufferedReader().readLines().forEach { s -> - if (s.startsWith(location) && s != location) - this += s - } - } - - private fun queryRegisterValue(location: String, name: String): String? { - val cmd = arrayOf("cmd", "/c", "reg", "query", location, "/v", name) - var last = false - val process = Runtime.getRuntime().exec(cmd) - process.waitFor() - process.inputStream.bufferedReader().readLines().forEach { s -> - if (s.isNotBlank()) { - if (last && s.trim().startsWith(name)) { - var begins = s.indexOf(name) - if (begins > 0) { - val s2 = s.substring(begins + name.length) - begins = s2.indexOf("REG_SZ") - if (begins > 0) - return s2.substring(begins + "REG_SZ".length).trim() - } - } - if (s.trim() == location) - last = true - } - } - return null - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Logging.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Logging.kt deleted file mode 100644 index 409ee7024..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Logging.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -@file:JvmName("HMCLog") -package org.jackhuang.hmcl.util - -import javafx.scene.paint.Color -import java.io.ByteArrayOutputStream -import java.io.PrintWriter -import java.text.SimpleDateFormat -import java.util.* -import java.util.logging.* -import java.util.logging.Formatter -import java.util.regex.Pattern - -val LOG = Logger.getLogger("HMCL").apply { - level = Level.FINER - useParentHandlers = false - addHandler(FileHandler("hmcl.log").apply { - level = Level.FINER - formatter = DefaultFormatter - }) - addHandler(ConsoleHandler().apply { - level = Level.FINER - formatter = DefaultFormatter - }) -} - -val DEFAULT_DATE_FORMAT = SimpleDateFormat("HH:mm:ss") - -internal object DefaultFormatter : Formatter() { - override fun format(record: LogRecord): String { - var s: String = "[${DEFAULT_DATE_FORMAT.format(Date(record.millis))}] [${record.sourceClassName}.${record.sourceMethodName}/${record.level.name}] ${record.message}\n" - val builder = ByteArrayOutputStream() - if (record.thrown != null) - PrintWriter(builder).use(record.thrown::printStackTrace) - s += builder.toString() - return s - } - -} - -/** - * - * @author huangyuhui - */ -enum class Log4jLevel constructor(val level: Int, val color: Color) { - - FATAL(1, Color.web("#F7A699")), - ERROR(2, Color.web("#FFCCBB")), - WARN(3, Color.web("#FFEECC")), - INFO(4, Color.web("#FBFBFB")), - DEBUG(5, Color.web("#EEE9E0")), - TRACE(6, Color.BLUE), - ALL(2147483647, Color.BLACK); - - fun lessOrEqual(level: Log4jLevel): Boolean { - return this.level <= level.level - } - - companion object { - - val MINECRAFT_LOGGER = Pattern.compile("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]") - val MINECRAFT_LOGGER_CATEGORY = Pattern.compile("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\] \\[(?[^\\]]+)\\]") - val JAVA_SYMBOL = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*" - - fun guessLevel(line: String): Log4jLevel? { - var level: Log4jLevel? = null - val m = MINECRAFT_LOGGER.matcher(line) - if (m.find()) { - // New style logs from log4j - val levelStr = m.group("level") - if (null != levelStr) - when (levelStr) { - "INFO" -> level = INFO - "WARN" -> level = WARN - "ERROR" -> level = ERROR - "FATAL" -> level = FATAL - "TRACE" -> level = TRACE - "DEBUG" -> level = DEBUG - else -> { - } - } - val m2 = MINECRAFT_LOGGER_CATEGORY.matcher(line) - if (m2.find()) { - val level2Str = m2.group("category") - if (null != level2Str) - when (level2Str) { - "STDOUT" -> level = INFO - "STDERR" -> level = ERROR - } - } - } else { - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") - || line.contains("[FINER]") || line.contains("[FINEST]")) - level = INFO - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) - level = ERROR - if (line.contains("[WARNING]")) - level = WARN - if (line.contains("[DEBUG]")) - level = DEBUG - } - return if (line.contains("overwriting existing")) FATAL else level - - /*if (line.contains("Exception in thread") - || line.matches("\\s+at " + JAVA_SYMBOL) - || line.matches("Caused by: " + JAVA_SYMBOL) - || line.matches("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)") - || line.matches("... \\d+ more$")) - return ERROR;*/ - } - - fun isError(a: Log4jLevel?): Boolean { - return a?.lessOrEqual(ERROR) ?: false - } - - fun mergeLevel(a: Log4jLevel?, b: Log4jLevel?): Log4jLevel? { - return if (a == null) b - else if (b == null) a - else if (a.level < b.level) a else b - } - } - -} - -fun guessLogLineError(log: String) = Log4jLevel.isError(Log4jLevel.guessLevel(log)) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ManagedProcess.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ManagedProcess.kt deleted file mode 100644 index 7afef7570..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ManagedProcess.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.util.* -import java.util.concurrent.ConcurrentLinkedQueue - -/** - * The managed process. - * - * @param process the raw system process that this instance manages. - * @param commands the command line of [process]. - * @see [org.jackhuang.hmcl.launch.ExitWaiter] - * @see [org.jackhuang.hmcl.launch.StreamPump] - */ -class ManagedProcess( - val process: Process, - val commands: List -) { - /** - * To save some information you need. - */ - val properties = mutableMapOf() - - /** - * The standard output/error lines. - */ - val lines: Queue = ConcurrentLinkedQueue() - - /** - * The related threads. - * - * If a thread is monitoring this raw process, - * you are required to add the instance to [relatedThreads]. - */ - val relatedThreads = mutableListOf() - - /** - * True if the managed process is running. - */ - val isRunning: Boolean = try { - process.exitValue() - true - } catch (ex: IllegalThreadStateException) { - false - } - - /** - * The exit code of raw process. - */ - val exitCode: Int get() = process.exitValue() - - /** - * Destroys the raw process and other related threads that are monitoring this raw process. - */ - fun stop() { - process.destroy() - relatedThreads.forEach(Thread::interrupt) - } - - override fun toString() = "ManagedProcess[commands=$commands, isRunning=$isRunning]" -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/NetUtils.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/NetUtils.kt deleted file mode 100644 index e948d070e..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/NetUtils.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream -import java.net.HttpURLConnection -import java.net.Proxy -import java.net.URL -import java.security.GeneralSecurityException -import java.security.SecureRandom -import java.security.cert.X509Certificate -import java.util.* -import java.util.logging.Level -import javax.net.ssl.HostnameVerifier -import javax.net.ssl.HttpsURLConnection -import javax.net.ssl.SSLContext -import javax.net.ssl.X509TrustManager - -private val XTM = object : X509TrustManager { - override fun checkClientTrusted(chain: Array, authType: String) {} - - override fun checkServerTrusted(chain: Array, authType: String) {} - - override fun getAcceptedIssuers() = arrayOfNulls(0) -} -private val HNV = HostnameVerifier { _, _ -> true } -@Volatile -private var INIT_HTTPS = false -fun initHttps() { - if (INIT_HTTPS) - return - INIT_HTTPS = true - System.setProperty("https.protocols", "SSLv3,TLSv1") - try { - val c = SSLContext.getInstance("SSL") - c.init(null, arrayOf(XTM), SecureRandom()) - HttpsURLConnection.setDefaultSSLSocketFactory(c.socketFactory) - } catch (e: GeneralSecurityException) { - } - HttpsURLConnection.setDefaultHostnameVerifier(HNV) -} - -var DEFAULT_USER_AGENT: () -> String = { RandomUserAgent.randomUserAgent } - -fun String.toURL() = URL(this) - -fun URL.createConnection(proxy: Proxy): HttpURLConnection { - initHttps() - return openConnection(proxy).apply { - doInput = true - useCaches = false - connectTimeout = 15000 - readTimeout = 15000 - addRequestProperty("User-Agent", DEFAULT_USER_AGENT()) - } as HttpURLConnection -} - -fun URL.doGet() = openConnection().getInputStream().readFullyAsString() -fun URL.doGet(proxy: Proxy) = createConnection(proxy).getInputStream().readFullyAsString() - -fun URL.doPost(post: Map, - contentType: String = "application/x-www-form-urlencoded", - proxy: Proxy = Proxy.NO_PROXY): String { - val sb = StringBuilder() - post.entries.forEach { (key, value) -> - sb.append(key).append('=').append(value).append('&') - } - if (sb.isNotEmpty()) - sb.deleteCharAt(sb.length - 1) - return doPost(sb.toString(), contentType, proxy) -} - -fun URL.doPost(post: String, - contentType: String = "application/x-www-form-urlencoded", - proxy: Proxy = Proxy.NO_PROXY): String { - val bytes = post.toByteArray(Charsets.UTF_8) - - val con = createConnection(proxy) - con.requestMethod = "POST" - con.doOutput = true - con.setRequestProperty("Content-Type", contentType + "; charset=utf-8") - con.setRequestProperty("Content-Length", "" + bytes.size) - var os: OutputStream? = null - try { - os = con.outputStream - os?.write(bytes) - } finally { - os?.closeQuietly() - } - return con.readData() -} - -fun HttpURLConnection.readData(): String { - var input: InputStream? = null - try { - input = inputStream - return input.readFullyAsString(Charsets.UTF_8) - } catch (e: IOException) { - input?.closeQuietly() - input = errorStream - return input?.readFullyAsString(Charsets.UTF_8) ?: throw e - } finally { - input?.closeQuietly() - } -} - -fun URL.detectFileNameQuietly(proxy: Proxy = Proxy.NO_PROXY): String { - try { - val conn = createConnection(proxy) - conn.connect() - if (conn.responseCode / 100 != 2) - throw IOException("Response code ${conn.responseCode}") - return conn.detectFileName() - } catch (e: IOException) { - LOG.log(Level.WARNING, "Cannot detect the file name of URL $this", e) - return UUIDTypeAdapter.fromUUID(UUID.randomUUID()) - } -} - -fun URL.detectFileName(proxy: Proxy = Proxy.NO_PROXY): String { - val conn = createConnection(proxy) - conn.connect() - if (conn.responseCode / 100 != 2) - throw IOException("Response code ${conn.responseCode}") - return conn.detectFileName() -} - -fun HttpURLConnection.detectFileName(): String { - val disposition = getHeaderField("Content-Disposition") - if (disposition == null || !disposition.contains("filename=")) { - val u = url.toString() - return u.substringAfterLast('/') - } else - return disposition.substringAfter("filename=").removeSurrounding("\"") -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/OS.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/OS.kt deleted file mode 100644 index 68d4a7d6c..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/OS.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import com.google.gson.annotations.SerializedName -import javafx.scene.input.Clipboard -import javafx.scene.input.ClipboardContent -import java.io.File -import java.lang.management.ManagementFactory -import java.nio.charset.Charset -import java.util.* - -/** - * Represents the operating system. - */ -enum class OS(val checkedName: String) { - /** - * Microsoft Windows. - */ - WINDOWS("windows"), - /** - * Linux and Unix like OS, including Solaris. - */ - LINUX("linux"), - /** - * Mac OS X. - */ - OSX("osx"), - /** - * Unknown operating system. - */ - UNKNOWN("universal"); - - companion object { - /** - * The current operating system. - */ - val CURRENT_OS: OS by lazy { - System.getProperty("os.name").toLowerCase(Locale.US).run { - when { - contains("win") -> WINDOWS - contains("mac") -> OSX - contains("solaris") || contains("linux") || contains("unix") || contains("sunos") -> LINUX - else -> UNKNOWN - } - } - } - - /** - * The total memory/MB this computer have. - */ - val TOTAL_MEMORY: Int by lazy { - val bytes = ManagementFactory.getOperatingSystemMXBean().call("getTotalPhysicalMemorySize") as? Long? - if (bytes == null) 1024 - else (bytes / 1024 / 1024).toInt() - } - - /** - * The suggested memory size/MB for Minecraft to allocate. - */ - val SUGGESTED_MEMORY: Int by lazy { - val memory = TOTAL_MEMORY / 4 - (Math.round(1.0 * memory / 128.0) * 128).toInt() - } - - val PATH_SEPARATOR: String = File.pathSeparator - val FILE_SEPARATOR: String = File.separator - val LINE_SEPARATOR: String by lazy(System::lineSeparator) - - /** - * The system default encoding. - */ - val ENCODING: String by lazy { - System.getProperty("sun.jnu.encoding", Charset.defaultCharset().name()) - } - - /** - * The version of current operating system. - */ - val SYSTEM_VERSION: String by lazy { System.getProperty("os.version") } - - /** - * The arch of current operating system. - */ - val SYSTEM_ARCH: String by lazy { System.getProperty("os.arch") } - - /** - * Set the content of clipboard. - */ - fun setClipboard(string: String) { - val clipboard = Clipboard.getSystemClipboard() - clipboard.setContent(ClipboardContent().apply { - putString(string) - }) - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Platform.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Platform.kt deleted file mode 100644 index b36fda8db..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Platform.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import com.google.gson.* -import java.lang.reflect.Type - -/** - * The platform that indicates which the platform of operating system is, 64-bit or 32-bit. - * Of course, 128-bit and 16-bit is not supported. - */ -enum class Platform(val bit: String) { - BIT_32("32"), - BIT_64("64"), - UNKNOWN("unknown"); - - /** - * The json serializer to [Platform] - */ - companion object Serializer: JsonSerializer, JsonDeserializer { - - override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Platform? { - if (json == null) return null - return when (json.asInt) { - 0 -> BIT_32 - 1 -> BIT_64 - else -> UNKNOWN - } - } - - override fun serialize(src: Platform?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement? { - if (src == null) return null - return when (src) { - BIT_32 -> JsonPrimitive(0) - BIT_64 -> JsonPrimitive(1) - UNKNOWN -> JsonPrimitive(-1) - } - } - - /** - * The platform of current Java Environment. - */ - val PLATFORM: Platform by lazy { - if (IS_64_BIT) BIT_64 else BIT_32 - } - - /** - * True if current Java Environment is 64-bit. - */ - val IS_64_BIT: Boolean by lazy { - val arch = System.getProperty("sun.arch.data.model") ?: System.getProperty("os.arch") - arch.contains("64") - } - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/RandomUserAgent.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/RandomUserAgent.kt deleted file mode 100644 index bc4377ae7..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/RandomUserAgent.kt +++ /dev/null @@ -1,1683 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.util.* - -object RandomUserAgent { - - private val uaMap = HashMap>() - private val freqMap = HashMap() - - init { - - freqMap.put("Internet Explorer", 11.8) - freqMap.put("Firefox", 28.2) - freqMap.put("Chrome", 52.9) - freqMap.put("Safari", 3.9) - freqMap.put("Opera", 1.8) - - uaMap.put("Internet Explorer", arrayOf( - "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/4.0; InfoPath.2; SV1; .NET CLR 2.0.50727; WOW64)", - "Mozilla/5.0 (compatible; MSIE 10.0; Macintosh; Intel Mac OS X 10_7_3; Trident/6.0)", - "Mozilla/4.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)", - "Mozilla/1.22 (compatible; MSIE 10.0; Windows 3.1)", - "Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))", - "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; Media Center PC 6.0; InfoPath.3; MS-RTC LM 8; Zune 4.7)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; Media Center PC 6.0; InfoPath.3; MS-RTC LM 8; Zune 4.7", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 4.0; InfoPath.3; MS-RTC LM 8; .NET4.0C; .NET4.0E)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; chromeframe/12.0.742.112)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 4.0; Tablet PC 2.0; InfoPath.3; .NET4.0C; .NET4.0E)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; yie8)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET CLR 1.1.4322; .NET4.0C; Tablet PC 2.0)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; FunWebProducts)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; chromeframe/13.0.782.215)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; chromeframe/11.0.696.57)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) chromeframe/10.0.648.205", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.1; SV1; .NET CLR 2.8.52393; WOW64; en-US)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0; chromeframe/11.0.696.57)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/4.0; GTB7.4; InfoPath.3; SV1; .NET CLR 3.1.76908; WOW64; en-US)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.8.36217; WOW64; en-US)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; .NET CLR 2.7.58687; SLCC2; Media Center PC 5.0; Zune 3.4; Tablet PC 3.6; InfoPath.3)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; SLCC1; .NET CLR 1.1.4322)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 3.0.04506.30)", - "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 5.0; Trident/4.0; FBSMTWB; .NET CLR 2.0.34861; .NET CLR 3.0.3746.3218; .NET CLR 3.5.33652; msn OptimizedIE8;ENUS)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.2; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; MS-RTC LM 8)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; MS-RTC LM 8", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; Media Center PC 6.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.3; .NET4.0C; .NET4.0E; .NET CLR 3.5.30729; .NET CLR 3.0.30729; MS-RTC LM 8)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 3.0)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; msn OptimizedIE8;ZHCN)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MS-RTC LM 8; InfoPath.3; .NET4.0C; .NET4.0E) chromeframe/8.0.552.224", - "Mozilla/4.0(compatible; MSIE 7.0b; Windows NT 6.0)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 6.0)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; Media Center PC 3.0; .NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.1)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; FDM; .NET CLR 1.1.4322)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; InfoPath.1)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; Alexa Toolbar; .NET CLR 2.0.50727)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; Alexa Toolbar)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.40607)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322)", - "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.0.3705; Media Center PC 3.1; Alexa Toolbar; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", - "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)", - "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; el-GR)", - "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 5.2)", - "Mozilla/5.0 (MSIE 7.0; Macintosh; U; SunOS; X11; gu; SV1; InfoPath.2; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)", - "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; c .NET CLR 3.0.04506; .NET CLR 3.5.30707; InfoPath.1; el-GR)", - "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; c .NET CLR 3.0.04506; .NET CLR 3.5.30707; InfoPath.1; el-GR)", - "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; fr-FR)", - "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; en-US)", - "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 5.2; WOW64; .NET CLR 2.0.50727)", - "Mozilla/5.0 (compatible; MSIE 7.0; Windows 98; SpamBlockerUtility 6.3.91; SpamBlockerUtility 6.2.91; .NET CLR 4.1.89;GB)", - "Mozilla/4.79 [en] (compatible; MSIE 7.0; Windows NT 5.0; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 1.1.4322; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)", - "Mozilla/4.0 (Windows; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", - "Mozilla/4.0 (Mozilla/4.0; MSIE 7.0; Windows NT 5.1; FDM; SV1; .NET CLR 3.0.04506.30)", - "Mozilla/4.0 (Mozilla/4.0; MSIE 7.0; Windows NT 5.1; FDM; SV1)", - "Mozilla/4.0 (compatible;MSIE 7.0;Windows NT 6.0)", - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; Win64; x64; Trident/6.0; .NET4.0E; .NET4.0C)", - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; SLCC2; .NET CLR 2.0.50727; InfoPath.3; .NET4.0C; .NET4.0E; .NET CLR 3.5.30729; .NET CLR 3.0.30729; MS-RTC LM 8)", - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MS-RTC LM 8; .NET4.0C; .NET4.0E; InfoPath.3)", - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)", - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; chromeframe/12.0.742.100)", - "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", - "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)", - "Mozilla/4.0 (compatible; MSIE 6.01; Windows NT 6.0)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.1; DigExt)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.1)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; YComp 5.0.2.6)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; YComp 5.0.0.0) (Compatible; ; ; Trident/4.0)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; YComp 5.0.0.0)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.1.4322)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 4.0; .NET CLR 1.0.2914)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 4.0)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98; YComp 5.0.0.0)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98; Win 9x 4.90)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.1)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.3705)", - "Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 4.0)", - "Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", - "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", - "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4325)", - "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)", - "Mozilla/45.0 (compatible; MSIE 6.0; Windows NT 5.1)", - "Mozilla/4.08 (compatible; MSIE 6.0; Windows NT 5.1)", - "Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)", - "Mozilla/4.0 (X11; MSIE 6.0; i686; .NET CLR 1.1.4322; .NET CLR 2.0.50727; FDM)", - "Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 6.0)", - "Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.2)", - "Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.0)", - "Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)", - "Mozilla/4.0 (MSIE 6.0; Windows NT 5.1)", - "Mozilla/4.0 (MSIE 6.0; Windows NT 5.0)", - "Mozilla/4.0 (compatible;MSIE 6.0;Windows 98;Q312461)", - "Mozilla/4.0 (Compatible; Windows NT 5.1; MSIE 6.0) (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", - "Mozilla/4.0 (compatible; U; MSIE 6.0; Windows NT 5.1) (Compatible; ; ; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", - "Mozilla/4.0 (compatible; U; MSIE 6.0; Windows NT 5.1)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3; Tablet PC 2.0)", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB6.5; QQDownload 534; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; SLCC2; .NET CLR 2.0.50727; Media Center PC 6.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729)", - "Mozilla/4.0 (compatible; MSIE 5.5b1; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.50; Windows NT; SiteKiosk 4.9; SiteCoach 1.0)", - "Mozilla/4.0 (compatible; MSIE 5.50; Windows NT; SiteKiosk 4.8; SiteCoach 1.0)", - "Mozilla/4.0 (compatible; MSIE 5.50; Windows NT; SiteKiosk 4.8)", - "Mozilla/4.0 (compatible; MSIE 5.50; Windows 98; SiteKiosk 4.8)", - "Mozilla/4.0 (compatible; MSIE 5.50; Windows 95; SiteKiosk 4.8)", - "Mozilla/4.0 (compatible;MSIE 5.5; Windows 98)", - "Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1)", - "Mozilla/4.0 (compatible; MSIE 5.5;)", - "Mozilla/4.0 (Compatible; MSIE 5.5; Windows NT5.0; Q312461; SV1; .NET CLR 1.1.4322; InfoPath.2)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT5)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 6.1; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 6.1; chromeframe/12.0.742.100; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30618)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.5)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.2; .NET CLR 1.1.4322; InfoPath.2; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; FDM)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.2; .NET CLR 1.1.4322) (Compatible; ; ; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.2; .NET CLR 1.1.4322)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)", - "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)", - "Mozilla/4.0 (compatible; MSIE 5.23; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.22; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.21; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.2; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.2; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.17; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.17; Mac_PowerPC Mac OS; en)", - "Mozilla/4.0 (compatible; MSIE 5.16; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.16; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.15; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.15; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.14; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.13; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.12; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.12; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.05; Windows NT 4.0)", - "Mozilla/4.0 (compatible; MSIE 5.05; Windows NT 3.51)", - "Mozilla/4.0 (compatible; MSIE 5.05; Windows 98; .NET CLR 1.1.4322)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; YComp 5.0.0.0)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; Hotbar 4.1.8.0)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; DigExt)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; .NET CLR 1.0.3705)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.6; MSIECrawler)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.6; Hotbar 4.2.8.0)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.6; Hotbar 3.0)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.6)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.2.4)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.0.0; Hotbar 4.1.8.0)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; YComp 5.0.0.0)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Wanadoo 5.6)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Wanadoo 5.3; Wanadoo 5.5)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Wanadoo 5.1)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; SV1; .NET CLR 1.1.4322; .NET CLR 1.0.3705; .NET CLR 2.0.50727)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; SV1)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Q312461; T312461)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; Q312461)", - "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; MSIECrawler)", - "Mozilla/4.0 (compatible; MSIE 5.0b1; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)", - "Mozilla/4.0(compatible; MSIE 5.0; Windows 98; DigExt)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT;)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; YComp 5.0.2.6)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; YComp 5.0.2.5)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; YComp 5.0.0.0)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; Hotbar 4.1.8.0)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; Hotbar 3.0)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt; .NET CLR 1.0.3705)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 6.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.0.04506.648; .NET4.0C; .NET4.0E)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.9; .NET CLR 1.1.4322)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.2; .NET CLR 1.1.4322)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98;)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; YComp 5.0.2.4)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; Hotbar 3.0)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt; YComp 5.0.2.6; yplus 1.0)", - "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt; YComp 5.0.2.6)", - "Mozilla/4.0 (compatible; MSIE 4.5; Windows NT 5.1; .NET CLR 2.0.40607)", - "Mozilla/4.0 (compatible; MSIE 4.5; Windows 98; )", - "Mozilla/4.0 (compatible; MSIE 4.5; Mac_PowerPC)", - "Mozilla/4.0 (compatible; MSIE 4.5; Mac_PowerPC)", - "Mozilla/4.0 PPC (compatible; MSIE 4.01; Windows CE; PPC; 240x320; Sprint:PPC-6700; PPC; 240x320)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows NT)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows NT 5.0)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint;PPC-i830; PPC; 240x320)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint; SCH-i830; PPC; 240x320)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:SPH-ip830w; PPC; 240x320)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:SPH-ip320; Smartphone; 176x220)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:SCH-i830; PPC; 240x320)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:SCH-i320; Smartphone; 176x220)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Sprint:PPC-i830; PPC; 240x320)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Smartphone; 176x220)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320; Sprint:PPC-6700; PPC; 240x320)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320; PPC)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98; Hotbar 3.0)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98; DigExt)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)", - "Mozilla/4.0 (compatible; MSIE 4.01; Windows 95)", - "Mozilla/4.0 (compatible; MSIE 4.01; Mac_PowerPC)", - "Mozilla/4.0 WebTV/2.6 (compatible; MSIE 4.0)", - "Mozilla/4.0 (compatible; MSIE 4.0; Windows NT)", - "Mozilla/4.0 (compatible; MSIE 4.0; Windows 98 )", - "Mozilla/4.0 (compatible; MSIE 4.0; Windows 95; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", - "Mozilla/4.0 (compatible; MSIE 4.0; Windows 95)", - "Mozilla/4.0 (Compatible; MSIE 4.0)", - "Mozilla/2.0 (compatible; MSIE 4.0; Windows 98)", - "Mozilla/2.0 (compatible; MSIE 3.03; Windows 3.1)", - "Mozilla/2.0 (compatible; MSIE 3.02; Windows 3.1)", - "Mozilla/2.0 (compatible; MSIE 3.01; Windows 95)", - "Mozilla/2.0 (compatible; MSIE 3.01; Windows 95)", - "Mozilla/2.0 (compatible; MSIE 3.0B; Windows NT)", - "Mozilla/3.0 (compatible; MSIE 3.0; Windows NT 5.0)", - "Mozilla/2.0 (compatible; MSIE 3.0; Windows 95)", - "Mozilla/2.0 (compatible; MSIE 3.0; Windows 3.1)", - "Mozilla/4.0 (compatible; MSIE 2.0; Windows NT 5.0; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", - "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)", - "Mozilla/1.22 (compatible; MSIE 2.0; Windows 3.1)")) - - uaMap.put("Firefox", arrayOf( - "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20120101 Firefox/29.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0", - "Mozilla/5.0 (X11; OpenBSD amd64; rv:28.0) Gecko/20100101 Firefox/28.0", - "Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0", - "Mozilla/5.0 (Windows NT 6.1; rv:27.3) Gecko/20130101 Firefox/27.3", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:27.0) Gecko/20121011 Firefox/27.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:25.0) Gecko/20100101 Firefox/25.0", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0", - "Mozilla/5.0 (Windows NT 6.0; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0", - "Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/23.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20130406 Firefox/23.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0", - "Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/22.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:22.0) Gecko/20130328 Firefox/22.0", - "Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.0", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:21.0.0) Gecko/20121011 Firefox/21.0.0", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (X11; Linux i686; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.2; rv:21.0) Gecko/20130326 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130401 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130331 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130330 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130401 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130328 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130401 Firefox/21.0", - "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130331 Firefox/21.0", - "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 5.0; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64;) Gecko/20100101 Firefox/20.0", - "Mozilla/5.0 (Windows x86; rv:19.0) Gecko/20100101 Firefox/19.0", - "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/19.0", - "Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20100101 Firefox/18.0.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0.6", - "Mozilla/5.0 (X11; Ubuntu; Linux armv7l; rv:17.0) Gecko/20100101 Firefox/17.0", - "Mozilla/6.0 (Windows NT 6.2; WOW64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1", - "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1", - "Mozilla/5.0 (X11; NetBSD amd64; rv:16.0) Gecko/20121102 Firefox/16.0", - "Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.16) Gecko/20120427 Firefox/15.0a1", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20120427 Firefox/15.0a1", - "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:15.0) Gecko/20120910144328 Firefox/15.0.2", - "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:15.0) Gecko/20121011 Firefox/15.0.1", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:14.0) Gecko/20120405 Firefox/14.0a1", - "Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20120405 Firefox/14.0a1", - "Mozilla/5.0 (Windows NT 5.1; rv:14.0) Gecko/20120405 Firefox/14.0a1", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1", - "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:14.0) Gecko/20100101 Firefox/14.0.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; WOW64; en-US; rv:2.0.4) Gecko/20120718 AskTbAVR-IDW/3.12.5.17700 Firefox/14.0.1", - "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/14.0.1", - "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/ 20120405 Firefox/14.0.1", - "Mozilla/5.0 (Windows NT 6.0; rv:14.0) Gecko/20100101 Firefox/14.0.1", - "Mozilla/5.0 (Windows NT 5.1; rv:15.0) Gecko/20100101 Firefox/13.0.1", - "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/12.0", - "Mozilla/5.0 (Windows NT 6.1; de;rv:12.0) Gecko/20120403211507 Firefox/12.0", - "Mozilla/5.0 (Windows NT 5.1; rv:12.0) Gecko/20120403211507 Firefox/12.0", - "Mozilla/5.0 (compatible; Windows; U; Windows NT 6.2; WOW64; en-US; rv:12.0) Gecko/20120403211507 Firefox/12.0", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.16) Gecko/20120421 Gecko Firefox/11.0", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.16) Gecko/20120421 Firefox/11.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:11.0) Gecko Firefox/11.0", - "Mozilla/5.0 (Windows NT 6.1; U;WOW64; de;rv:11.0) Gecko Firefox/11.0", - "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0", - "Mozilla/6.0 (Macintosh; I; Intel Mac OS X 11_7_9; de-LI; rv:1.9b4) Gecko/2012010317 Firefox/10.0a4", - "Mozilla/5.0 (Macintosh; I; Intel Mac OS X 11_7_9; de-LI; rv:1.9b4) Gecko/2012010317 Firefox/10.0a4", - "Mozilla/5.0 (X11; Mageia; Linux x86_64; rv:10.0.9) Gecko/20100101 Firefox/10.0.9", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0a2) Gecko/20111101 Firefox/9.0a2", - "Mozilla/5.0 (Windows NT 6.2; rv:9.0.1) Gecko/20100101 Firefox/9.0.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0", - "Mozilla/5.0 (Windows NT 5.1; rv:8.0; en_us) Gecko/20100101 Firefox/8.0", - "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/7.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110613 Firefox/6.0a2", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110612 Firefox/6.0a2", - "Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0", - "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20110814 Firefox/6.0", - "Mozilla/5.0 (Windows NT 5.1; rv:6.0) Gecko/20100101 Firefox/6.0 FirePHP/0.6", - "Mozilla/5.0 (Windows NT 5.0; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0", - "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:5.0a2) Gecko/20110524 Firefox/5.0a2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.28) Gecko/20120306 Firefox/5.0.1", - "Mozilla/5.0 (Windows NT 6.1; U; ru; rv:5.0.1.6) Gecko/20110501 Firefox/5.0.1 Firefox/5.0.1", - "mozilla/3.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/5.0.1", - "Mozilla/5.0 (X11; U; Linux i586; de; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (X11; U; Linux amd64; rv:5.0) Gecko/20100101 Firefox/5.0 (Debian)", - "Mozilla/5.0 (X11; U; Linux amd64; en-US; rv:5.0) Gecko/20110619 Firefox/5.0", - "Mozilla/5.0 (X11; Linux) Gecko Firefox/5.0", - "Mozilla/5.0 (X11; Linux x86_64; rv:5.0) Gecko/20100101 Firefox/5.0 FirePHP/0.5", - "Mozilla/5.0 (X11; Linux x86_64; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0", - "Mozilla/5.0 (X11; Linux x86_64) Gecko Firefox/5.0", - "Mozilla/5.0 (X11; Linux ppc; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (X11; Linux AMD64) Gecko Firefox/5.0", - "Mozilla/5.0 (X11; FreeBSD amd64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:5.0) Gecko/20110619 Firefox/5.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 6.1.1; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.2; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.1; U; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.0; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.0; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.2a1pre) Gecko/20110324 Firefox/4.2a1pre", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.2a1pre) Gecko/20100101 Firefox/4.2a1pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110324 Firefox/4.2a1pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110323 Firefox/4.2a1pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110208 Firefox/4.2a1pre", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.0b9pre) Gecko/20110111 Firefox/4.0b9pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b9pre) Gecko/20101228 Firefox/4.0b9pre", - "Mozilla/5.0 (Windows NT 5.1; rv:2.0b9pre) Gecko/20110105 Firefox/4.0b9pre", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b8pre) Gecko/20101114 Firefox/4.0b8pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101213 Firefox/4.0b8pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101128 Firefox/4.0b8pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101114 Firefox/4.0b8pre", - "Mozilla/5.0 (Windows NT 5.1; rv:2.0b8pre) Gecko/20101127 Firefox/4.0b8pre", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8", - "Mozilla/4.0 (compatible; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8)", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b7pre) Gecko/20100921 Firefox/4.0b7pre", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b7) Gecko/20101111 Firefox/4.0b7", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b7) Gecko/20100101 Firefox/4.0b7", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre Firefox/4.0b6pre", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.0b4) Gecko/20100818 Firefox/4.0b4", - "Mozilla/5.0 (X11; Linux i686; rv:2.0b3pre) Gecko/20100731 Firefox/4.0b3pre", - "Mozilla/5.0 (Windows NT 5.2; rv:2.0b13pre) Gecko/20110304 Firefox/4.0b13pre", - "Mozilla/5.0 (Windows NT 5.1; rv:2.0b13pre) Gecko/20110223 Firefox/4.0b13pre", - "Mozilla/5.0 (X11; Linux i686; rv:2.0b12pre) Gecko/20110204 Firefox/4.0b12pre", - "Mozilla/5.0 (X11; Linux i686; rv:2.0b12pre) Gecko/20100101 Firefox/4.0b12pre", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b11pre) Gecko/20110128 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110131 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110129 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110128 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b11pre) Gecko/20110126 Firefox/4.0b11pre", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b11pre) Gecko/20110126 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b10pre) Gecko/20110118 Firefox/4.0b10pre", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b10pre) Gecko/20110113 Firefox/4.0b10pre", - "Mozilla/5.0 (X11; Linux i686; rv:2.0b10) Gecko/20100101 Firefox/4.0b10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:2.0b10) Gecko/20110126 Firefox/4.0b10", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b10) Gecko/20110126 Firefox/4.0b10", - "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.1.3) Gecko/20091020 Ubuntu/10.04 (lucid) Firefox/4.0.1", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.0.1) Gecko/20110506 Firefox/4.0.1", - "Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20110518 Firefox/4.0.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:2.0.1) Gecko/20110606 Firefox/4.0.1", - "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:2.0) Gecko/20110307 Firefox/4.0", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:2.0) Gecko/20110404 Fedora/16-dev Firefox/4.0", - "Mozilla/5.0 (X11; Arch Linux i686; rv:2.0) Gecko/20110321 Firefox/4.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0) Gecko/20110319 Firefox/4.0", - "Mozilla/5.0 (Windows NT 6.1; rv:1.9) Gecko/20100101 Firefox/4.0", - "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/20121223 Ubuntu/9.25 (jaunty) Firefox/3.8", - "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", - "Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20100401 Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", - "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.3a5pre) Gecko/20100526 Firefox/3.7a5pre", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2) Gecko/20091218 Firefox 3.6b5", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2a1pre) Gecko/20090428 Firefox/3.6a1pre", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2a1pre) Gecko/20090405 Firefox/3.6a1pre", - "Mozilla/5.0 (X11; U; Linux i686; ru-RU; rv:1.9.2a1pre) Gecko/20090405 Ubuntu/9.04 (jaunty) Firefox/3.6a1pre", - "Mozilla/5.0 (Windows; Windows NT 5.1; es-ES; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre", - "Mozilla/5.0 (Windows; Windows NT 5.1; en-US; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.9) Gecko/20100915 Gentoo Firefox/3.6.9", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.9) Gecko/20100827 Red Hat/3.6.9-2.el6 Firefox/3.6.9", - "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.9.2.9) Gecko/20100913 Firefox/3.6.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; rv:1.9.2.9) Gecko/20100913 Firefox/3.6.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9 ( .NET CLR 3.5.30729; .NET CLR 4.0.20506)", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6;en-US; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9", - "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.2.8) Gecko/20101230 Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100804 Gentoo Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100723 SUSE/3.6.8-0.1.1 Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.2.8) Gecko/20100722 Ubuntu/10.04 (lucid) Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; fi-FI; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.8) Gecko/20100727 Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; de-DE; rv:1.9.2.8) Gecko/20100725 Gentoo Firefox/3.6.8", - "Mozilla/5.0 (X11; U; FreeBSD i386; de-CH; rv:1.9.2.8) Gecko/20100729 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.8) Gecko/20100722 AskTbADAP/3.9.1.14019 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.8) Gecko/20100722 Firefox 3.6.8 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0C)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.8) Gecko/20100722 Firefox 3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.3) Gecko/20121221 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-TW; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100809 Fedora/3.6.7-1.fc14 Firefox/3.6.7", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100723 Fedora/3.6.7-1.fc13 Firefox/3.6.7", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.7) Gecko/20100726 CentOS/3.6-3.el5.centos Firefox/3.6.7", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.7 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-PT; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.0", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-PT; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; de-AT; rv:1.9.1.8) Gecko/20100625 Firefox/3.6.6", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.4) Gecko/20100614 Ubuntu/10.04 (lucid) Firefox/3.6.4", - "Mozilla/5.0 (X11; U; Linux i686; fa; rv:1.8.1.4) Gecko/20100527 Firefox/3.6.4", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.4) Gecko/20100625 Gentoo Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-TW; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-CA; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.0 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100503 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; nb-NO; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3pre) Gecko/20100405 Firefox/3.6.3plugin1 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; he; rv:1.9.1b4pre) Gecko/20100405 Firefox/3.6.3plugin1", - "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.3) Gecko/20100403 Fedora/3.6.3-4.fc13 Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100403 Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.3) Gecko/20100401 SUSE/3.6.3-1.1 Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux i686; ko-KR; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100404 Ubuntu/10.04 (lucid) Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux AMD64; en-US; rv:1.9.2.3) Gecko/20100403 Ubuntu/10.10 (maverick) Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; pl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.0 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ca; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.2.28) Gecko/20120306 Firefox/3.6.28", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.28) Gecko/20120306 AskTbSTC-SRS/3.13.1.18132 Firefox/3.6.28 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.28) Gecko/20120306 Firefox/3.6.28 ( .NET CLR 3.5.30729; .NET4.0C)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2.25) Gecko/20111212 Firefox/3.6.25 ( .NET CLR 3.5.30729; .NET4.0C)", - "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.24) Gecko/20111101 SUSE/3.6.24-0.2.1 Firefox/3.6.24", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.24) Gecko/20111103 Firefox/3.6.24", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.24) Gecko/20111103 Firefox/3.6.24", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; fr; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; en-US; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; it; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.21) Gecko/20110830 Ubuntu/10.10 (maverick) Firefox/3.6.21", - "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.20) Gecko/20110805 Ubuntu/10.04 (lucid) Firefox/3.6.20", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.20) Gecko/20110804 Red Hat/3.6-2.el5 Firefox/3.6.20", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; hu; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.20) Gecko/20110803 AskTbFWV5/3.13.0.17701 Firefox/3.6.20 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; cs; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 GTB7.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.2) Gecko/20100316 AskTbSPC2/3.9.1.14019 Firefox/3.6.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 GTB6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.0.04506.648)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.0.04506.30)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.7; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.10pre) Gecko/20100902 Ubuntu/9.10 (karmic) Firefox/3.6.1pre", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.19", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; en-GB; rv:1.9.2.19) Gecko/20110707 Firefox/3.6.19", - "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", - "Mozilla/5.0 (X11; U; Linux i686; pl; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.18) Gecko/20110615 Ubuntu/10.10 (maverick) Firefox/3.6.18", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ar; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; pt-BR; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-GB; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", - "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:5.0) Gecko/20100101 Firefox/3.6.17 Firefox/3.6.17", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; sl; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (X11; U; Linux x86_64; ja-JP; rv:1.9.2.16) Gecko/20110323 Ubuntu/10.10 (maverick) Firefox/3.6.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16) Gecko/20110323 Ubuntu/9.10 (karmic) Firefox/3.6.16 FirePHP/0.5", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en; rv:1.9.1.13) Gecko/20100914 Firefox/3.6.16", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.16) Gecko/20110319 AskTbUTR/3.11.3.15590 Firefox/3.6.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16pre) Gecko/20110304 Ubuntu/10.10 (maverick) Firefox/3.6.15pre", - "Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.9.2.15) Gecko/20110303 Ubuntu/8.04 (hardy) Firefox/3.6.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.15) Gecko/20110303 Ubuntu/10.04 (lucid) Firefox/3.6.15 FirePHP/0.5", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.15) Gecko/20110330 CentOS/3.6-1.el5.centos Firefox/3.6.15", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C) FirePHP/0.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.15) Gecko/20110303 AskTbBT4/3.11.3.15590 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.14pre) Gecko/20110105 Firefox/3.6.14pre", - "Mozilla/5.0 (X11; U; Linux armv7l; en-US; rv:1.9.2.14) Gecko/20110224 Firefox/3.6.14 MB860/Version.0.43.3.MB860.AmericaMovil.en.MX", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-AU; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14 GTB7.1 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.13) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; nb-NO; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.13) Gecko/20110103 Fedora/3.6.13-1.fc14 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101223 Gentoo Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101219 Gentoo Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101206 Red Hat/3.6-3.el4 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101206 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-NZ; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.13) Gecko/20101206 Ubuntu/9.10 (karmic) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.13) Gecko/20101206 Red Hat/3.6-2.el5 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; da-DK; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux MIPS32 1074Kf CPS QuadCore; en-US; rv:1.9.2.13) Gecko/20110103 Fedora/3.6.13-1.fc14 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; pt-BR; rv:1.9.2.13) Gecko/20101209 Fedora/3.6.13-1.fc13 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.2.13) Gecko/20101206 Ubuntu/9.10 (karmic) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.13) Gecko/20101209 CentOS/3.6-2.el5.centos Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.9.2.12) Gecko/20101030 Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; es-MX; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.04 (lucid) Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.2.12) Gecko/20101027 Fedora/3.6.12-1.fc13 Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.2.12) Gecko/20101026 SUSE/3.6.12-0.7.1 Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101102 Gentoo Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101102 Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux ppc; fr; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux i686; ko-KR; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101114 Gentoo Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12 GTB7.1", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.12) Gecko/20101027 Fedora/3.6.12-1.fc13 Firefox/3.6.12", - "Mozilla/5.0 (X11; FreeBSD x86_64; rv:2.0) Gecko/20100101 Firefox/3.6.12", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; sv-SE; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 (.NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; .NET CLR 3.5.21022)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; de; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 GTB5", - "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.2.11) Gecko/20101028 CentOS/3.6-2.el5.centos Firefox/3.6.11", - "Mozilla/5.0 (X11; U; Linux armv7l; en-GB; rv:1.9.2.3pre) Gecko/20100723 Firefox/3.6.11", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; ru; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux x86_64; pt-BR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10 GTB7.1", - "Mozilla/5.0 (X11; U; Linux x86_64; el-GR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10 GTB7.1", - "Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; es-AR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.10) Gecko/20100915 Ubuntu/9.04 (jaunty) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.11) Gecko/20101013 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; en-CA; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100915 Ubuntu/9.10 (karmic) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100914 SUSE/3.6.10-0.3.1 Firefox/3.6.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ro; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; nl; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.1) Gecko/20100122 firefox/3.6.1", - "Mozilla/5.0(Windows; U; Windows NT 7.0; rv:1.9.2) Gecko/20100101 Firefox/3.6", - "Mozilla/5.0(Windows; U; Windows NT 5.2; rv:1.9.2) Gecko/20100101 Firefox/3.6", - "Mozilla/5.0 (X11; U; x86_64 Linux; en_GB, en_US; rv:1.9.2) Gecko/20100115 Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100222 Ubuntu/10.04 (lucid) Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100130 Gentoo Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2) Gecko/20100308 Ubuntu/10.04 (lucid) Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.2pre) Gecko/20100312 Ubuntu/9.04 (jaunty) Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100128 Gentoo Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100115 Ubuntu/10.04 (lucid) Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6 FirePHP/0.4", - "Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/3.6", - "Mozilla/5.0 (X11; FreeBSD i686) Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU; rv:1.9.2) Gecko/20100105 MRA 5.6 (build 03278) Firefox/3.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; lt; rv:1.9.2) Gecko/20100115 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.3a3pre) Gecko/20100306 Firefox3.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.8) Gecko/20100806 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.3) Gecko/20100401 Firefox/3.6;MEGAUPLOAD 1.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ar; rv:1.9.2) Gecko/20100115 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.2) Gecko/20100115 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b5pre) Gecko/20090517 Firefox/3.5b4pre (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Gentoo Linux x86_64; pl-PL) Gecko Firefox", - "Mozilla/5.0 (X11; ; Linux x86_64; rv:1.8.1.6) Gecko/20070802 Firefox", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.6) Gecko/2009011913 Firefox", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE; rv:1.9.2.20) Gecko/20110803 Firefox", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; rv:1.8.1.16) Gecko/20080702 Firefox", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.13) Gecko/20080313 Firefox")) - - uaMap.put("Chrome", arrayOf( - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36", - "Mozilla/5.0 (X11; CrOS i686 4319.74.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1467.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1500.55 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.90 Safari/537.36", - "Mozilla/5.0 (X11; NetBSD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36", - "Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.14 (KHTML, like Gecko) Chrome/24.0.1292.0 Safari/537.14", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1284.0 Safari/537.13", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.6 Safari/537.11", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.6 Safari/537.11", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.26 Safari/537.11", - "Mozilla/5.0 (Windows NT 6.0) yi; AppleWebKit/345667.12221 (KHTML, like Gecko) Chrome/23.0.1271.26 Safari/453667.1221", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.17 Safari/537.11", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_0) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", - "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.22 (KHTML, like Gecko) Chrome/19.0.1047.0 Safari/535.22", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.21 (KHTML, like Gecko) Chrome/19.0.1042.0 Safari/535.21", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.21 (KHTML, like Gecko) Chrome/19.0.1041.0 Safari/535.21", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/18.6.872.0 Safari/535.2 UNTRUSTED/1.0 3gpp-gba UNTRUSTED/1.0", - "Mozilla/5.0 (Macintosh; AMD Mac OS X 10_8_2) AppleWebKit/535.22 (KHTML, like Gecko) Chrome/18.6.872", - "Mozilla/5.0 (X11; CrOS i686 1660.57.0) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.46 Safari/535.19", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.45 Safari/535.19", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.45 Safari/535.19", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.45 Safari/535.19", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.151 Safari/535.19", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.19 (KHTML, like Gecko) Ubuntu/11.10 Chromium/18.0.1025.142 Chrome/18.0.1025.142 Safari/535.19", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.11 Safari/535.19", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.66 Safari/535.11", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/17.0.963.65 Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.04 Chromium/17.0.963.65 Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/10.10 Chromium/17.0.963.65 Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/17.0.963.65 Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (X11; FreeBSD amd64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.65 Safari/535.11", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.04 Chromium/17.0.963.56 Chrome/17.0.963.56 Safari/535.11", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.12 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/17.0.940.0 Safari/535.8", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.77 Safari/535.7ad-imcjapan-syosyaman-xkgi3lqg03!wgz", - "Mozilla/5.0 (X11; CrOS i686 1193.158.0) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7xs5D9rRDFpg2g", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.8", - "Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.6 (KHTML, like Gecko) Chrome/16.0.897.0 Safari/535.6", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.54 Safari/535.2", - "Mozilla/5.0 (X11; FreeBSD i386) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.121 Safari/535.2", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.10 Chromium/15.0.874.120 Chrome/15.0.874.120 Safari/535.2", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.120 Safari/535.2", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.872.0 Safari/535.2", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.04 Chromium/15.0.871.0 Chrome/15.0.871.0 Safari/535.2", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.864.0 Safari/535.2", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.860.0 Safari/535.2", - "Chrome/15.0.860.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/15.0.860.0", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.186 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.834.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.825.0 Chrome/14.0.825.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.824.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.815.10913 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.815.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.814.0 Chrome/14.0.814.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.814.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.813.0 Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.812.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.811.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.810.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.810.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.809.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.10 Chromium/14.0.808.0 Chrome/14.0.808.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.808.0 Chrome/14.0.808.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.804.0 Chrome/14.0.804.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.803.0 Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.801.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.801.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.794.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.794.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.790.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.790.0 Safari/535.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1) AppleWebKit/526.3 (KHTML, like Gecko) Chrome/14.0.564.21 Safari/526.3", - "Mozilla/5.0 (X11; CrOS i686 13.587.48) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.43 Safari/535.1", - "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41", - "Mozilla/5.0 ArchLinux (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/13.0.782.41 Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.32 Safari/535.1", - "Mozilla/5.0 (X11; Linux amd64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", - "Mozilla/5.0 (X11; CrOS i686 0.13.587) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.14 Safari/535.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.107 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.107 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.1 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.36 (KHTML, like Gecko) Chrome/13.0.766.0 Safari/534.36", - "Mozilla/5.0 (X11; Linux amd64) AppleWebKit/534.36 (KHTML, like Gecko) Chrome/13.0.766.0 Safari/534.36", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.35 (KHTML, like Gecko) Ubuntu/10.10 Chromium/13.0.764.0 Chrome/13.0.764.0 Safari/534.35", - "Mozilla/5.0 (X11; CrOS i686 0.13.507) AppleWebKit/534.35 (KHTML, like Gecko) Chrome/13.0.763.0 Safari/534.35", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.33 (KHTML, like Gecko) Ubuntu/9.10 Chromium/13.0.752.0 Chrome/13.0.752.0 Safari/534.33", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.31 (KHTML, like Gecko) Chrome/13.0.748.0 Safari/534.31", - "Mozilla/5.0 (Windows NT 6.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.750.0 Safari/534.30", - "Mozilla/5.0 (X11; CrOS i686 12.433.109) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.93 Safari/534.30", - "Mozilla/5.0 (X11; CrOS i686 12.0.742.91) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.93 Safari/534.30", - "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/12.0.742.91", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.91 Chromium/12.0.742.91 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30", - "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.60 Safari/534.30", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.53 Safari/534.30", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.113 Safari/534.30", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/11.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/11.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Windows NT 7.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Windows 8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; CrOS i686 12.433.216) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.105 Safari/534.30", - "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.724.100 Safari/534.30", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.25 (KHTML, like Gecko) Chrome/12.0.706.0 Safari/534.25", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.25 (KHTML, like Gecko) Chrome/12.0.704.0 Safari/534.25", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.703.0 Chrome/12.0.703.0 Safari/534.24", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.702.0 Chrome/12.0.702.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/12.0.702.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/12.0.702.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.700.3 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.699.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.699.0 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.698.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.697.0 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.71 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", - "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/11.0.696.50", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.43 Safari/534.24", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.14 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.12 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.12 Safari/534.24", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.04 Chromium/11.0.696.0 Chrome/11.0.696.0 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.694.0 Safari/534.24", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.23 (KHTML, like Gecko) Chrome/11.0.686.3 Safari/534.23", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.682.0 Safari/534.21", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_0; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", - "Mozilla/5.0 (Windows NT) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.669.0 Safari/534.20", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.19 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.19", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.18", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.660.0 Safari/534.18", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.654.0 Safari/534.17", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.652.0 Safari/534.17", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.82 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux armv7l; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", - "Mozilla/5.0 (X11; U; FreeBSD x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", - "Mozilla/5.0 (X11; U; FreeBSD i386; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.133 Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.133 Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.127 Chrome/10.0.648.127 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU; AppleWebKit/534.16; KHTML; like Gecko; Chrome/10.0.648.11;Safari/534.16)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.0 Chrome/10.0.648.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.0 Chrome/10.0.648.0 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.642.0 Chrome/10.0.642.0 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.639.0 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.638.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.634.0 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.634.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 SUSE/10.0.626.0 (KHTML, like Gecko) Chrome/10.0.626.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.613.0 Safari/534.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.613.0 Chrome/10.0.613.0 Safari/534.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.04 Chromium/10.0.612.3 Chrome/10.0.612.3 Safari/534.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.612.1 Safari/534.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.611.0 Chrome/10.0.611.0 Safari/534.15", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.602.0 Safari/534.14", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML,like Gecko) Chrome/9.1.0.0 Safari/540.0", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML, like Gecko) Ubuntu/10.10 Chrome/9.1.0.0 Safari/540.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Ubuntu/10.10 Chromium/9.0.600.0 Chrome/9.0.600.0 Safari/534.14", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.600.0 Safari/534.14", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.599.0 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-CA) AppleWebKit/534.13 (KHTML like Gecko) Chrome/9.0.597.98 Safari/534.13", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.84 Safari/534.13", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.44 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.19 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.107 Safari/534.13 v1333515017.9196", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.596.0 Safari/534.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Ubuntu/10.04 Chromium/9.0.595.0 Chrome/9.0.595.0 Safari/534.13", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Ubuntu/9.10 Chromium/9.0.592.0 Chrome/9.0.592.0 Safari/534.13", - "Mozilla/5.0 (X11; U; Windows NT 6; en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.587.0 Safari/534.12", - "Mozilla/5.0 (Windows U Windows NT 5.1 en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.583.0 Safari/534.12", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.579.0 Safari/534.12", - "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.576.0 Safari/534.12", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML, like Gecko) Ubuntu/10.10 Chrome/8.1.0.0 Safari/540.0", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.558.0 Safari/534.10", - "Mozilla/5.0 (X11; U; CrOS i686 0.9.130; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.344 Safari/534.10", - "Mozilla/5.0 (X11; U; CrOS i686 0.9.128; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.343 Safari/534.10", - "Mozilla/5.0 (X11; U; CrOS i686 0.9.128; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.341 Safari/534.10", - "Mozilla/5.0 (X11; U; CrOS i686 0.9.128; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.339 Safari/534.10", - "Mozilla/5.0 (X11; U; CrOS i686 0.9.128; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.339", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Ubuntu/10.10 Chromium/8.0.552.237 Chrome/8.0.552.237 Safari/534.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/533.3", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.210 Safari/534.10", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.200 Safari/534.10", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.551.0 Safari/534.10", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.548.0 Safari/534.10", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.544.0 Safari/534.10", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.15) Gecko/20101027 Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.540.0 Safari/534.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.540.0 Safari/534.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.540.0 Safari/534.10", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.540.0 Safari/534.10", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.9 (KHTML, like Gecko) Chrome/7.0.531.0 Safari/534.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.8 (KHTML, like Gecko) Chrome/7.0.521.0 Safari/534.8", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.24 Safari/534.7", - "Mozilla/5.0 (X11; U; Linux x86_64; fr-FR) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.500.0 Safari/534.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.498.0 Safari/534.6", - "Mozilla/5.0 (ipad Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.6 (KHTML, like Gecko) Chrome/7.0.498.0 Safari/534.6", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/7.0.0 Safari/700.13", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.4 (KHTML, like Gecko) Chrome/6.0.481.0 Safari/534.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.53 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.33 Safari/534.3", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.470.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.464.0 Safari/534.3", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.464.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.463.0 Safari/534.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.462.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.462.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.460.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.460.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.460.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.459.0 Safari/534.3", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.1 Safari/534.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.458.0 Safari/534.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.457.0 Safari/534.3", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.456.0 Safari/534.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.454.0 Safari/534.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.454.0 Safari/534.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.453.1 Safari/534.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.453.1 Safari/534.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.453.1 Safari/534.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.2 (KHTML, like Gecko) Chrome/6.0.451.0 Safari/534.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.1 SUSE/6.0.428.0 (KHTML, like Gecko) Chrome/6.0.428.0 Safari/534.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.428.0 Safari/534.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.428.0 Safari/534.1", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.428.0 Safari/534.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.427.0 Safari/534.1", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.422.0 Safari/534.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.417.0 Safari/534.1", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.416.0 Safari/534.1", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.1 (KHTML, like Gecko) Chrome/6.0.414.0 Safari/534.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.9 (KHTML, like Gecko) Chrome/6.0.400.0 Safari/533.9", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.8 (KHTML, like Gecko) Chrome/6.0.397.0 Safari/533.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/6.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.999 Safari/533.4", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_0; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.86 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.86 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_0; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.86 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.70 Safari/533.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.127 Safari/533.4", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.126 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; fr-FR) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.126 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.125 Safari/533.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.370.0 Safari/533.4", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.368.0 Safari/533.4", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.366.2 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.366.0 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.366.0 Safari/533.4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.363.0 Safari/533.3", - "Mozilla/5.0 (X11; U; OpenBSD i386; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.359.0 Safari/533.3", - "Mozilla/5.0 (X11; U; x86_64 Linux; en_GB, en_US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.358.0 Safari/533.3", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.358.0 Safari/533.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.358.0 Safari/533.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.357.0 Safari/533.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.356.0 Safari/533.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.355.0 Safari/533.3", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.354.0 Safari/533.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.354.0 Safari/533.3", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.353.0 Safari/533.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Chrome/5.0.353.0 Safari/533.3", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.343.0 Safari/533.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.343.0 Safari/533.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_0; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.7 Safari/533.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.7 Safari/533.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.5 Safari/533.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.2 Safari/533.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", - "Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.1 (KHTML, like Gecko) Chrome/5.0.335.0 Safari/533.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/533.16 (KHTML, like Gecko) Chrome/5.0.335.0 Safari/533.16", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.310.0 Safari/532.9", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.309.0 Safari/532.9", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.308.0 Safari/532.9", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_0; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.307.11 Safari/532.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.307.1 Safari/532.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.1.249.1025 Safari/532.5", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.8 (KHTML, like Gecko) Chrome/4.0.302.2 Safari/532.8", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.8 (KHTML, like Gecko) Chrome/4.0.288.1 Safari/532.8", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.8 (KHTML, like Gecko) Chrome/4.0.277.0 Safari/532.8", - "Mozilla/5.0 (X11; U; Slackware Linux x86_64; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.30 Safari/532.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; it-IT) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.25 Safari/532.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_8; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.246.0 Safari/532.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.4 (KHTML, like Gecko) Chrome/4.0.241.0 Safari/532.4", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.4 (KHTML, like Gecko) Chrome/4.0.237.0 Safari/532.4 Debian", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.3 (KHTML, like Gecko) Chrome/4.0.227.0 Safari/532.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.3 (KHTML, like Gecko) Chrome/4.0.224.2 Safari/532.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.3 (KHTML, like Gecko) Chrome/4.0.223.5 Safari/532.3", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.4 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.3 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE) Chrome/4.0.223.3 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.2 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.2 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.2 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.2 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.1 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.1 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.1 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.223.0 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.8 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.7 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.6 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.6 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.6 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.5 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.5 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.5 Safari/532.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.5 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.4 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.4 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.4 Safari/532.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.4 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.3 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.3 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.3 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.2 Safari/532.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.2 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.12 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.12 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.12 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.1 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.0 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.8 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.8 Safari/532.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.8 Safari/532.2", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.8 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.7 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.6 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.6 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.6 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.3 Safari/532.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.221.0 Safari/532.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.220.1 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.5 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.5 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.4 Safari/532.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.3 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.3 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.3 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.219.0 Safari/532.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.1 Safari/532.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.0 Safari/532.1", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.0 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.0 Safari/532.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.213.0 Safari/532.1", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_0; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.212.1 Safari/532.1", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.212.1 Safari/532.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.1 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.1", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.212.0 Safari/532.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.7 Safari/532.0", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.7 Safari/532.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.4 Safari/532.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.4 Safari/532.0", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.4 Safari/532.0", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.211.2 Safari/532.0")) - - uaMap.put("Safari", arrayOf( - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fi-fi) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es-es) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en_CA) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/419 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; tr-tr) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8_Adobe", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.9.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nb-no) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nb-no) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.9.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/417.9 (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.9.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418 (KHTML, like Gecko) Safari/417.9.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nl-nl) AppleWebKit/416.11 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; nb-no) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13_Adobe", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/416.12 (KHTML, like Gecko) Safari/412.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-ca) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/416.11 (KHTML, like Gecko) Safari/416.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/416.11 (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13_Adobe", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/416.12 (KHTML, like Gecko) Safari/416.13", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.6", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5_Adobe", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.7 (KHTML, like Gecko) Safari/412.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS; pl-pl) AppleWebKit/412 (KHTML, like Gecko) Safari/412", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS; en-en) AppleWebKit/412 (KHTML, like Gecko) Safari/412", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/412 (KHTML, like Gecko) Safari/412", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/412 (KHTML, like Gecko) Safari/412", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es-ES) AppleWebKit/412 (KHTML, like Gecko) Safari/412", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en_US) AppleWebKit/412 (KHTML, like Gecko) Safari/412", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412 (KHTML, like Gecko) Safari/412 Privoxy/3.0", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412 (KHTML, like Gecko) Safari/412", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.6.2 (KHTML, like Gecko) Safari/412.2.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.6.2 (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412 (KHTML, like Gecko) Safari/412", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.6.2 (KHTML, like Gecko) Safari/412.2.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2_Adobe", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.6 (KHTML, like Gecko) Safari/412.2", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412.6 (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/412 (KHTML, like Gecko) Safari/412", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.6", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.6", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.8.1 (KHTML, like Gecko) Safari/312.6", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.6", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.6", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.3.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.8.1 (KHTML, like Gecko) Safari/312.6", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5_Adobe", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.5 (KHTML, like Gecko) Safari/312.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/312.5 (KHTML, like Gecko) Safari/312.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es-es) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.5 (KHTML, like Gecko) Safari/312.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/125", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5 (KHTML, like Gecko) Safari/312.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.5.2 (KHTML, like Gecko) Safari/312.3.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.1.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/312.1 (KHTML, like Gecko) Safari/125", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-ch) AppleWebKit/312.1.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-ca) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1 (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.1.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.1.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312.3.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-ch) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.11", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-ch) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-ch) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.11", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.5.7 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.11", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.7 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.5.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.11", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.7 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12_Adobe", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.6 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12_Adobe", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.5.5 (KHTML, like Gecko) Safari/125.12", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.5 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en_CA) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-au) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.5 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.4 (KHTML, like Gecko) Safari/100", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.4 (KHTML, like Gecko) Safari/125.9", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; es-es) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.7", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-gb) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/85.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.7", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.7", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; it-it) AppleWebKit/124 (KHTML, like Gecko) Safari/125.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/124 (KHTML, like Gecko) Safari/125", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/124 (KHTML, like Gecko) Safari/125", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/124 (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/124 (KHTML, like Gecko) Safari/125.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/124 (KHTML, like Gecko) Safari/125", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85.8.2 (KHTML, like Gecko) Safari/85.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-gb) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/85.8.2 (KHTML, like Gecko) Safari/85.8.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85.8.1", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.8.5 (KHTML, like Gecko) Safari/85", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.8.2 (KHTML, like Gecko) Safari/85.8", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; sv-se) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr-fr) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fr) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.6", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.7", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092816 Mobile Safari 1.1.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/533+ (KHTML, like Gecko)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/528.8 (KHTML, like Gecko)", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.34 (KHTML, like Gecko) Dooble/1.40 Safari/534.34", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; fi-fi) AppleWebKit/420+ (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.8.1 (KHTML, like Gecko) Safari/312.6", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/419.2 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-ch) AppleWebKit/85 (KHTML, like Gecko) Safari/85", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-CH) AppleWebKit/419.2 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; da-dk) AppleWebKit/522+ (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_6; en-us) AppleWebKit/528.16 (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; it-IT) AppleWebKit/521.25 (KHTML, like Gecko) Safari/521.24", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-us) AppleWebKit/419.2.1 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/522.11.1 (KHTML, like Gecko) Safari/419.3", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/521.32.1 (KHTML, like Gecko) Safari/521.32.1", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; es-es) AppleWebKit/531.22.7 (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-us) AppleWebKit/528.16 (KHTML, like Gecko)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_5; it-it) AppleWebKit/525.18 (KHTML, like Gecko)")) - - uaMap.put("Opera", arrayOf( - "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14", - "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.14", - "Opera/12.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.02", - "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00", - "Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00", - "Opera/12.0(Windows NT 5.2;U;en)Presto/22.9.168 Version/12.00", - "Opera/12.0(Windows NT 5.1;U;en)Presto/22.9.168 Version/12.00", - "Mozilla/5.0 (Windows NT 5.1) Gecko/20100101 Firefox/14.0 Opera/12.0", - "Opera/9.80 (Windows NT 6.1; WOW64; U; pt) Presto/2.10.229 Version/11.62", - "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.10.229 Version/11.62", - "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", - "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; de) Presto/2.9.168 Version/11.52", - "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.9.168 Version/11.51", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; de) Opera 11.51", - "Opera/9.80 (X11; Linux x86_64; U; fr) Presto/2.9.168 Version/11.50", - "Opera/9.80 (X11; Linux i686; U; hu) Presto/2.9.168 Version/11.50", - "Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 Version/11.11", - "Opera/9.80 (X11; Linux i686; U; es-ES) Presto/2.8.131 Version/11.11", - "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/5.0 Opera 11.11", - "Opera/9.80 (X11; Linux x86_64; U; bg) Presto/2.8.131 Version/11.10", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.8.99 Version/11.10", - "Opera/9.80 (Windows NT 5.1; U; zh-tw) Presto/2.8.131 Version/11.10", - "Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1", - "Opera/9.80 (X11; Linux x86_64; U; Ubuntu/10.10 (maverick); pl) Presto/2.7.62 Version/11.01", - "Opera/9.80 (X11; Linux i686; U; ja) Presto/2.7.62 Version/11.01", - "Opera/9.80 (X11; Linux i686; U; fr) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; sv) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 5.1; U;) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.7.62 Version/11.01", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101213 Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", - "Mozilla/5.0 (Windows NT 6.1; U; nl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01", - "Mozilla/5.0 (Windows NT 6.1; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; de) Opera 11.01", - "Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00", - "Opera/9.80 (X11; Linux i686; U; it) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.37 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; ko) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; en-GB) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1 x64; U; en) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.7.39 Version/11.00", - "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.7.39 Version/11.00", - "Opera/9.80 (Windows NT 5.1; U; MRA 5.5 (build 02842); ru) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 5.1; U; it) Presto/2.7.62 Version/11.00", - "Mozilla/5.0 (Windows NT 6.0; U; ja; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", - "Mozilla/5.0 (Windows NT 5.1; U; pl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", - "Mozilla/5.0 (Windows NT 5.1; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; fr) Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; ja) Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; en) Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; pl) Opera 11.00", - "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.6.31 Version/10.70", - "Mozilla/5.0 (Windows NT 5.2; U; ru; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.70", - "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.70", - "Opera/9.80 (Windows NT 5.2; U; zh-cn) Presto/2.6.30 Version/10.63", - "Opera/9.80 (Windows NT 5.2; U; en) Presto/2.6.30 Version/10.63", - "Opera/9.80 (Windows NT 5.1; U; MRA 5.6 (build 03278); ru) Presto/2.6.30 Version/10.63", - "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.6.30 Version/10.62", - "Mozilla/5.0 (X11; Linux x86_64; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.62", - "Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; de) Opera 10.62", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; en) Opera 10.62", - "Opera/9.80 (X11; Linux i686; U; pl) Presto/2.6.30 Version/10.61", - "Opera/9.80 (X11; Linux i686; U; es-ES) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows NT 6.0; U; it) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows 98; U; de) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Macintosh; Intel Mac OS X; U; nl) Presto/2.6.30 Version/10.61", - "Opera/9.80 (X11; Linux i686; U; en) Presto/2.5.27 Version/10.60", - "Opera/9.80 (Windows NT 6.0; U; nl) Presto/2.6.30 Version/10.60", - "Opera/10.60 (Windows NT 5.1; U; zh-cn) Presto/2.6.30 Version/10.60", - "Opera/10.60 (Windows NT 5.1; U; en-US) Presto/2.6.30 Version/10.60", - "Opera/9.80 (X11; Linux i686; U; it) Presto/2.5.24 Version/10.54", - "Opera/9.80 (X11; Linux i686; U; en-GB) Presto/2.5.24 Version/10.53", - "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", - "Mozilla/5.0 (Windows NT 5.1; U; Firefox/5.0; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", - "Mozilla/5.0 (Windows NT 5.1; U; Firefox/4.5; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", - "Mozilla/5.0 (Windows NT 5.1; U; Firefox/3.5; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; ko) Opera 10.53", - "Opera/9.80 (Windows NT 6.1; U; fr) Presto/2.5.24 Version/10.52", - "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.5.22 Version/10.51", - "Opera/9.80 (Windows NT 6.0; U; cs) Presto/2.5.22 Version/10.51", - "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.5.22 Version/10.51", - "Opera/9.80 (Linux i686; U; en) Presto/2.5.22 Version/10.51", - "Mozilla/5.0 (Windows NT 6.1; U; en-GB; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.51", - "Mozilla/5.0 (Linux i686; U; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.51", - "Mozilla/4.0 (compatible; MSIE 8.0; Linux i686; en) Opera 10.51", - "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 6.1; U; sk) Presto/2.6.22 Version/10.50", - "Opera/9.80 (Windows NT 6.1; U; ja) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 6.0; U; zh-cn) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 5.1; U; sk) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.5.22 Version/10.50", - "Opera/10.50 (Windows NT 6.1; U; en-GB) Presto/2.2.2", - "Opera/9.80 (S60; SymbOS; Opera Tablet/9174; U; en) Presto/2.7.81 Version/10.5", - "Opera/9.80 (X11; U; Linux i686; en-US; rv:1.9.2.3) Presto/2.2.15 Version/10.10", - "Opera/9.80 (X11; Linux x86_64; U; it) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 6.1; U; de) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 6.0; U; Gecko/20100115; pl) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 5.1; U; de) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.2.15 Version/10.10", - "Mozilla/5.0 (Windows NT 6.0; U; tr; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 10.10", - "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux i686; de) Opera 10.10", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0; tr) Opera 10.10", - "Opera/9.80 (X11; Linux x86_64; U; en-GB) Presto/2.2.15 Version/10.01", - "Opera/9.80 (X11; Linux x86_64; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux x86_64; U; de) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; ru) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; pt-BR) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; pl) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; nb) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; en-GB) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; Debian; pl) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; de) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; de) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.0; U; de) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 5.2; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.2.15 Version/10.00", - "Opera/9.99 (X11; U; sk)", - "Opera/9.99 (Windows NT 5.1; U; pl) Presto/9.9.9", - "Opera/9.80 (J2ME/MIDP; Opera Mini/5.0 (Windows; U; Windows NT 5.1; en) AppleWebKit/886; U; en) Presto/2.4.15", - "Opera/9.70 (Linux ppc64 ; U; en) Presto/2.2.1", - "Opera/9.70 (Linux i686 ; U; zh-cn) Presto/2.2.0", - "Opera/9.70 (Linux i686 ; U; en-us) Presto/2.2.0", - "Opera/9.70 (Linux i686 ; U; en) Presto/2.2.1", - "Opera/9.70 (Linux i686 ; U; en) Presto/2.2.0", - "Opera/9.70 (Linux i686 ; U; ; en) Presto/2.2.1", - "Opera/9.70 (Linux i686 ; U; ; en) Presto/2.2.1", - "Mozilla/5.0 (Linux i686 ; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.70", - "Mozilla/4.0 (compatible; MSIE 6.0; Linux i686 ; en) Opera 9.70", - "HTC_HD2_T8585 Opera/9.70 (Windows NT 5.1; U; de)", - "Opera 9.7 (Windows NT 5.2; U; en)", - "Opera/9.64(Windows NT 5.1; U; en) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; pl) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; hr) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; en-GB) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; en) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; de) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; cs) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; tr) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; sv) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; pl) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; nb) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; Linux Mint; nb) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; Linux Mint; it) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; en) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; de) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; da) Presto/2.1.1", - "Opera/9.64 (Windows NT 6.1; U; MRA 5.5 (build 02842); ru) Presto/2.1.1", - "Opera/9.64 (Windows NT 6.1; U; de) Presto/2.1.1", - "Opera/9.64 (Windows NT 6.0; U; zh-cn) Presto/2.1.1", - "Opera/9.64 (Windows NT 6.0; U; pl) Presto/2.1.1", - "Opera/9.63 (X11; Linux x86_64; U; ru) Presto/2.1.1", - "Opera/9.63 (X11; Linux x86_64; U; cs) Presto/2.1.1")) - } - - val randomUserAgent: String - get() { - - val rand = Math.random() * 100 - var browser: String? = null - var count = 0.0 - for ((key, value) in freqMap) { - count += value - if (rand <= count) { - browser = key - break - } - } - - if (browser == null) { - browser = "Chrome" - } - - val userAgents = uaMap[browser]!! - return userAgents[Math.floor(Math.random() * userAgents.size).toInt()] - } -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/SimpleMultimap.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/SimpleMultimap.kt deleted file mode 100644 index 2d58ddbb1..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/SimpleMultimap.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.util.* - -/** - * A simple implementation of Multimap. - * Just a combination of map and set. - */ -class SimpleMultimap(mapper: () -> MutableMap>, private val valuer: () -> MutableCollection) { - private val map = mapper() - private val valuesImpl: MutableCollection = valuer() - - val size = valuesImpl.size - - val keys: Set get() = map.keys - val values: Collection = valuesImpl - - val isEmpty: Boolean = size == 0 - val isNotEmpty: Boolean = size != 0 - - fun containsKey(key: K): Boolean = map[key]?.isNotEmpty() ?: false - operator fun get(key: K): Collection = map.getOrPut(key, valuer) - - fun put(key: K, value: V) { - val set = map.getOrPut(key, valuer) - set += value - valuesImpl += value - } - - fun removeAll(key: K): Collection? { - val result = map.remove(key) - if (result != null) - valuesImpl.removeAll(result) - return result - } - - fun remove(value: V) { - map.values.forEach { - it.remove(value) - } - } - - fun clear() { - map.clear() - valuesImpl.clear() - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/VersionNumber.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/VersionNumber.kt deleted file mode 100644 index 589d05a76..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/VersionNumber.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.util.* - -/** - * The formatted version number represents a version string. - */ -abstract class VersionNumber: Comparable { - companion object { - @JvmStatic - fun asIntVersionNumber(version: String): IntVersionNumber { - if (version.count { it != '.' && (it < '0' || it > '9') } > 0 || version.contains("..") || version.trim().isBlank()) - throw IllegalArgumentException("The version $version is malformed, only dots and digits are allowed.") - val s = version.split(".") - var last = s.size - 1 - for (i in s.size - 1 downTo 0) - if (s[i].toInt() == 0) - last = i - val versions = ArrayList(last + 1) - for (i in 0..last) - versions.add(s[i].toInt()) - return IntVersionNumber(Collections.unmodifiableList(versions)) - } - - @JvmStatic - fun asVersion(version: String): VersionNumber { - try { - return asIntVersionNumber(version) - } catch (e: IllegalArgumentException) { - return StringVersionNumber(version) - } - } - } -} - -/** - * If a version string contains alphabets, a [StringVersionNumber] will be constructed. - */ -class StringVersionNumber internal constructor(val version: String): VersionNumber() { - override fun compareTo(other: VersionNumber): Int { - if (other !is StringVersionNumber) return 0 - return version.compareTo(other.version) - } - - override fun hashCode() = version.hashCode() - - override fun toString() = version - - override fun equals(other: Any?): Boolean { - val other1 = other as? VersionNumber ?: return false - val other2 = other1 as? StringVersionNumber ?: return true - return version == other2.version - } -} - -/** - * If a version string formats x.x.x.x, a [IntVersionNumber] will be generated. - */ -class IntVersionNumber internal constructor(val version: List): VersionNumber() { - - operator fun get(index: Int) = version[index] - - override fun compareTo(other: VersionNumber): Int { - if (other !is IntVersionNumber) return 0 - val len = minOf(this.version.size, other.version.size) - for (i in 0 until len) - if (version[i] != other.version[i]) - return version[i].compareTo(other.version[i]) - return this.version.size.compareTo(other.version.size) - } - - override fun hashCode(): Int { - var hash = 3 - for (v in version) hash = 83 * hash + v - return hash - } - - override fun toString(): String { - val builder = StringBuilder() - for (i in version.indices) builder.append(version[i]).append('.') - if (builder.isNotEmpty()) - builder.deleteCharAt(builder.length - 1) - return builder.toString() - } - - override fun equals(other: Any?): Boolean { - val other1 = other as? VersionNumber ?: return false - val other2 = other1 as? IntVersionNumber ?: return true - if (version.size != other2.version.size) return false - for (i in 0 until version.size) - if (version[i] != other2.version[i]) - return false - return true - } - -} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ZipEngine.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ZipEngine.kt deleted file mode 100644 index 00fc06751..000000000 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ZipEngine.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.util - -import java.util.zip.ZipEntry -import java.util.HashSet -import java.io.* -import java.util.zip.ZipOutputStream - - -/** - * Non thread-safe - * - * @author huangyuhui - */ -class ZipEngine - @Throws(IOException::class) - constructor(f: File) : Closeable { - - val buf = ByteArray(1024) - val zos: ZipOutputStream = ZipOutputStream(BufferedOutputStream(f.outputStream())) - private val names = HashSet() - - @Throws(IOException::class) - override fun close() { - zos.closeEntry() - zos.close() - } - - /** - * 功能:把 sourceDir 目录下的所有文件进行 zip 格式的压缩,保存为指定 zip 文件 - * - * @param sourceDir 源文件夹 - * @param pathNameCallback callback(pathName, isDirectory) returns your - * modified pathName - * - * @throws java.io.IOException 压缩失败或无法读取 - */ - @Throws(IOException::class) - @JvmOverloads - fun putDirectory(sourceDir: File, pathNameCallback: ((String, Boolean) -> String?)? = null) { - putDirectoryImpl(sourceDir, if (sourceDir.isDirectory) sourceDir.path else sourceDir.parent, pathNameCallback) - } - - /** - * 将文件压缩成zip文件 - * - * @param source zip文件路径 - * @param basePath 待压缩文件根目录 - * @param zos zip文件的os - * @param pathNameCallback callback(pathName, isDirectory) returns your - * modified pathName, null if you dont want this file zipped - */ - @Throws(IOException::class) - private fun putDirectoryImpl(source: File, basePath: String, pathNameCallback: ((String, Boolean) -> String?)?) { - val files: Array? - if (source.isDirectory) - files = source.listFiles() - else - files = arrayOf(source) - if (files == null) - return - var pathName: String? //存相对路径(相对于待压缩的根目录) - for (file in files) - if (file.isDirectory) { - pathName = file.path.substring(basePath.length + 1) + "/" - pathName = pathName.replace('\\', '/') - if (pathNameCallback != null) - pathName = pathNameCallback(pathName, true) - if (pathName == null) - continue - put(ZipEntry(pathName)) - putDirectoryImpl(file, basePath, pathNameCallback) - } else { - if (".DS_Store" == file.name) // For Mac computers. - continue - pathName = file.path.substring(basePath.length + 1) - pathName = pathName.replace('\\', '/') - if (pathNameCallback != null) - pathName = pathNameCallback(pathName, false) - if (pathName == null) - continue - putFile(file, pathName) - } - } - - @Throws(IOException::class) - fun putFile(file: File, pathName: String) = - file.inputStream().use { putStream(it, pathName) } - - @Throws(IOException::class) - fun putStream(inputStream: InputStream, pathName: String) { - put(ZipEntry(pathName)) - inputStream.copyTo(zos, buf) - } - - @Throws(IOException::class) - fun putTextFile(text: String, pathName: String) = - putTextFile(text, "UTF-8", pathName) - - @Throws(IOException::class) - fun putTextFile(text: String, encoding: String, pathName: String) = - putStream(ByteArrayInputStream(text.toByteArray(charset(encoding))), pathName) - - @Throws(IOException::class) - fun put(entry: ZipEntry) { - if (names.add(entry.name)) - zos.putNextEntry(entry) - } - -}