From f517e5ea31f1749617ac191137891cf87111550b Mon Sep 17 00:00:00 2001 From: Ben Soares Date: Mon, 15 Apr 2019 10:25:36 +0100 Subject: [PATCH] JAL-3130 adapted getdown src. attempt 2. first attempt failed due to cp'ed .git files --- getdown/src/getdown | 1 - getdown/src/getdown/.project | 17 + .../.settings/org.eclipse.core.resources.prefs | 2 + .../getdown/.settings/org.eclipse.m2e.core.prefs | 4 + getdown/src/getdown/.travis.yml | 11 + getdown/src/getdown/AUTHORS | 12 + getdown/src/getdown/CHANGELOG.md | 180 ++ getdown/src/getdown/LICENSE | 24 + getdown/src/getdown/README.md | 111 ++ getdown/src/getdown/ant/.project | 23 + .../ant/.settings/org.eclipse.core.resources.prefs | 3 + .../ant/.settings/org.eclipse.jdt.core.prefs | 6 + .../ant/.settings/org.eclipse.m2e.core.prefs | 4 + getdown/src/getdown/ant/pom.xml | 28 + .../com/threerings/getdown/tools/DigesterTask.java | 94 + .../ant/target/getdown-ant-1.8.3-SNAPSHOT.jar | Bin 0 -> 3331 bytes .../ant/target/maven-archiver/pom.properties | 5 + .../compile/default-compile/createdFiles.lst | 1 + .../compile/default-compile/inputFiles.lst | 1 + getdown/src/getdown/bin/differ | 4 + getdown/src/getdown/bin/patcher | 4 + getdown/src/getdown/core/.project | 23 + .../.settings/org.eclipse.core.resources.prefs | 6 + .../core/.settings/org.eclipse.jdt.core.prefs | 6 + .../core/.settings/org.eclipse.m2e.core.prefs | 4 + getdown/src/getdown/core/pom.xml | 132 ++ .../com/threerings/getdown/tests/DigesterIT.java | 54 + .../core/src/it/resources/testapp/background.png | Bin 0 -> 32808 bytes .../src/it/resources/testapp/crazyhashfile#txt | 1 + .../getdown/core/src/it/resources/testapp/foo.jar | Bin 0 -> 670 bytes .../resources/testapp/funny%test dir/some=file.txt | 1 + .../core/src/it/resources/testapp/getdown.txt | 28 + .../core/src/it/resources/testapp/script.sh | 3 + .../core/src/it/resources/testapp/testapp.jar | Bin 0 -> 823 bytes .../src/main/java/com/threerings/getdown/Log.java | 141 ++ .../threerings/getdown/cache/GarbageCollector.java | 99 ++ .../threerings/getdown/cache/ResourceCache.java | 80 + .../com/threerings/getdown/data/Application.java | 1826 ++++++++++++++++++++ .../java/com/threerings/getdown/data/Build.java | 43 + .../com/threerings/getdown/data/Build.java.tmpl | 43 + .../com/threerings/getdown/data/ClassPath.java | 76 + .../java/com/threerings/getdown/data/Digest.java | 228 +++ .../com/threerings/getdown/data/EnvConfig.java | 229 +++ .../com/threerings/getdown/data/PathBuilder.java | 135 ++ .../com/threerings/getdown/data/Properties.java | 19 + .../java/com/threerings/getdown/data/Resource.java | 394 +++++ .../java/com/threerings/getdown/data/SysProps.java | 185 ++ .../com/threerings/getdown/net/Downloader.java | 229 +++ .../com/threerings/getdown/net/HTTPDownloader.java | 115 ++ .../java/com/threerings/getdown/spi/ProxyAuth.java | 32 + .../java/com/threerings/getdown/tools/Differ.java | 232 +++ .../com/threerings/getdown/tools/Digester.java | 129 ++ .../java/com/threerings/getdown/tools/JarDiff.java | 449 +++++ .../com/threerings/getdown/tools/JarDiffCodes.java | 24 + .../threerings/getdown/tools/JarDiffPatcher.java | 292 ++++ .../java/com/threerings/getdown/tools/Patcher.java | 205 +++ .../java/com/threerings/getdown/util/Base64.java | 731 ++++++++ .../java/com/threerings/getdown/util/Color.java | 27 + .../java/com/threerings/getdown/util/Config.java | 378 ++++ .../threerings/getdown/util/ConnectionUtil.java | 73 + .../java/com/threerings/getdown/util/FileUtil.java | 239 +++ .../com/threerings/getdown/util/HostWhitelist.java | 54 + .../com/threerings/getdown/util/LaunchUtil.java | 251 +++ .../com/threerings/getdown/util/MessageUtil.java | 144 ++ .../getdown/util/ProgressAggregator.java | 50 + .../threerings/getdown/util/ProgressObserver.java | 18 + .../com/threerings/getdown/util/Rectangle.java | 40 + .../com/threerings/getdown/util/StreamUtil.java | 96 + .../com/threerings/getdown/util/StringUtil.java | 206 +++ .../com/threerings/getdown/util/VersionUtil.java | 114 ++ .../getdown/cache/GarbageCollectorTest.java | 71 + .../getdown/cache/ResourceCacheTest.java | 72 + .../com/threerings/getdown/data/ClassPathTest.java | 54 + .../com/threerings/getdown/data/EnvConfigTest.java | 142 ++ .../threerings/getdown/data/PathBuilderTest.java | 70 + .../com/threerings/getdown/data/SysPropsTest.java | 63 + .../com/threerings/getdown/util/ColorTest.java | 23 + .../com/threerings/getdown/util/ConfigTest.java | 171 ++ .../com/threerings/getdown/util/FileUtilTest.java | 60 + .../threerings/getdown/util/HostWhitelistTest.java | 159 ++ .../threerings/getdown/util/StringUtilTest.java | 28 + .../threerings/getdown/util/VersionUtilTest.java | 53 + .../org.mockito.plugins.MockMaker | 1 + .../src/getdown/core/target/antrun/build-main.xml | 15 + getdown/src/getdown/core/target/classes/LICENSE | 24 + .../core/target/getdown-core-1.8.3-SNAPSHOT.jar | Bin 0 -> 137977 bytes .../core/target/maven-archiver/pom.properties | 5 + .../compile/default-compile/createdFiles.lst | 63 + .../compile/default-compile/inputFiles.lst | 35 + .../default-testCompile/createdFiles.lst | 15 + .../testCompile/default-testCompile/inputFiles.lst | 13 + ...reerings.getdown.cache.GarbageCollectorTest.xml | 72 + ....threerings.getdown.cache.ResourceCacheTest.xml | 71 + ...T-com.threerings.getdown.data.ClassPathTest.xml | 69 + ...T-com.threerings.getdown.data.EnvConfigTest.xml | 73 + ...com.threerings.getdown.data.PathBuilderTest.xml | 69 + ...ST-com.threerings.getdown.data.SysPropsTest.xml | 70 + .../TEST-com.threerings.getdown.util.ColorTest.xml | 68 + ...TEST-com.threerings.getdown.util.ConfigTest.xml | 69 + ...ST-com.threerings.getdown.util.FileUtilTest.xml | 70 + ...m.threerings.getdown.util.HostWhitelistTest.xml | 68 + ...-com.threerings.getdown.util.StringUtilTest.xml | 68 + ...com.threerings.getdown.util.VersionUtilTest.xml | 72 + ...reerings.getdown.cache.GarbageCollectorTest.txt | 4 + ....threerings.getdown.cache.ResourceCacheTest.txt | 4 + .../com.threerings.getdown.data.ClassPathTest.txt | 4 + .../com.threerings.getdown.data.EnvConfigTest.txt | 4 + ...com.threerings.getdown.data.PathBuilderTest.txt | 4 + .../com.threerings.getdown.data.SysPropsTest.txt | 4 + .../com.threerings.getdown.util.ColorTest.txt | 4 + .../com.threerings.getdown.util.ConfigTest.txt | 4 + .../com.threerings.getdown.util.FileUtilTest.txt | 4 + ...m.threerings.getdown.util.HostWhitelistTest.txt | 4 + .../com.threerings.getdown.util.StringUtilTest.txt | 4 + ...com.threerings.getdown.util.VersionUtilTest.txt | 4 + .../org.mockito.plugins.MockMaker | 1 + getdown/src/getdown/launcher/.project | 23 + .../.settings/org.eclipse.core.resources.prefs | 4 + .../launcher/.settings/org.eclipse.jdt.core.prefs | 6 + .../launcher/.settings/org.eclipse.m2e.core.prefs | 4 + getdown/src/getdown/launcher/pom.xml | 176 ++ .../threerings/getdown/launcher/AbortPanel.java | 100 ++ .../com/threerings/getdown/launcher/Getdown.java | 1071 ++++++++++++ .../threerings/getdown/launcher/GetdownApp.java | 253 +++ .../getdown/launcher/MultipleGetdownRunning.java | 20 + .../threerings/getdown/launcher/ProxyPanel.java | 195 +++ .../com/threerings/getdown/launcher/ProxyUtil.java | 210 +++ .../getdown/launcher/RotatingBackgrounds.java | 132 ++ .../threerings/getdown/launcher/StatusPanel.java | 396 +++++ .../com/threerings/getdown/messages.properties | 110 ++ .../com/threerings/getdown/messages_de.properties | 116 ++ .../com/threerings/getdown/messages_es.properties | 115 ++ .../com/threerings/getdown/messages_fr.properties | 111 ++ .../com/threerings/getdown/messages_it.properties | 114 ++ .../com/threerings/getdown/messages_ja.properties | 107 ++ .../com/threerings/getdown/messages_ko.properties | 102 ++ .../com/threerings/getdown/messages_pt.properties | 118 ++ .../com/threerings/getdown/messages_zh.properties | 61 + .../com/threerings/getdown/messages.properties | 110 ++ .../com/threerings/getdown/messages_de.properties | 116 ++ .../com/threerings/getdown/messages_es.properties | 115 ++ .../com/threerings/getdown/messages_fr.properties | 111 ++ .../com/threerings/getdown/messages_it.properties | 114 ++ .../com/threerings/getdown/messages_ja.properties | 107 ++ .../com/threerings/getdown/messages_ko.properties | 102 ++ .../com/threerings/getdown/messages_pt.properties | 118 ++ .../com/threerings/getdown/messages_zh.properties | 61 + .../target/getdown-launcher-1.8.3-SNAPSHOT.jar | Bin 0 -> 195005 bytes ...tdown-launcher-1.8.3-SNAPSHOT_proguard_base.jar | Bin 0 -> 73485 bytes .../launcher/target/maven-archiver/pom.properties | 4 + .../compile/default-compile/createdFiles.lst | 28 + .../compile/default-compile/inputFiles.lst | 8 + .../src/getdown/launcher/target/proguard_map.txt | 1349 +++++++++++++++ .../src/getdown/launcher/target/proguard_seed.txt | 879 ++++++++++ getdown/src/getdown/lib/SOURCE_HEADER | 5 + getdown/src/getdown/lib/jRegistryKey.dll | Bin 0 -> 29316 bytes .../jregistrykey/1.0/jregistrykey-1.0.jar | Bin 0 -> 9980 bytes .../jregistrykey/1.0/jregistrykey-1.0.pom | 9 + .../jregistrykey/maven-metadata-local.xml | 12 + getdown/src/getdown/lib/manifest.mf | 6 + getdown/src/getdown/pom.xml | 180 ++ 161 files changed, 17421 insertions(+), 1 deletion(-) delete mode 160000 getdown/src/getdown create mode 100644 getdown/src/getdown/.project create mode 100644 getdown/src/getdown/.settings/org.eclipse.core.resources.prefs create mode 100644 getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs create mode 100644 getdown/src/getdown/.travis.yml create mode 100644 getdown/src/getdown/AUTHORS create mode 100644 getdown/src/getdown/CHANGELOG.md create mode 100644 getdown/src/getdown/LICENSE create mode 100644 getdown/src/getdown/README.md create mode 100644 getdown/src/getdown/ant/.project create mode 100644 getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs create mode 100644 getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs create mode 100644 getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs create mode 100644 getdown/src/getdown/ant/pom.xml create mode 100644 getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java create mode 100644 getdown/src/getdown/ant/target/getdown-ant-1.8.3-SNAPSHOT.jar create mode 100644 getdown/src/getdown/ant/target/maven-archiver/pom.properties create mode 100644 getdown/src/getdown/ant/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 getdown/src/getdown/ant/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100755 getdown/src/getdown/bin/differ create mode 100755 getdown/src/getdown/bin/patcher create mode 100644 getdown/src/getdown/core/.project create mode 100644 getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs create mode 100644 getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs create mode 100644 getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs create mode 100644 getdown/src/getdown/core/pom.xml create mode 100644 getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java create mode 100644 getdown/src/getdown/core/src/it/resources/testapp/background.png create mode 100644 getdown/src/getdown/core/src/it/resources/testapp/crazyhashfile#txt create mode 100644 getdown/src/getdown/core/src/it/resources/testapp/foo.jar create mode 100644 getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt create mode 100644 getdown/src/getdown/core/src/it/resources/testapp/getdown.txt create mode 100644 getdown/src/getdown/core/src/it/resources/testapp/script.sh create mode 100644 getdown/src/getdown/core/src/it/resources/testapp/testapp.jar create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java create mode 100644 getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java create mode 100644 getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java create mode 100644 getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 getdown/src/getdown/core/target/antrun/build-main.xml create mode 100644 getdown/src/getdown/core/target/classes/LICENSE create mode 100644 getdown/src/getdown/core/target/getdown-core-1.8.3-SNAPSHOT.jar create mode 100644 getdown/src/getdown/core/target/maven-archiver/pom.properties create mode 100644 getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst create mode 100644 getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.GarbageCollectorTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.ResourceCacheTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.ClassPathTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.EnvConfigTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.PathBuilderTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.SysPropsTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ColorTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ConfigTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.FileUtilTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.HostWhitelistTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.StringUtilTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.VersionUtilTest.xml create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.GarbageCollectorTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.ResourceCacheTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.ClassPathTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.EnvConfigTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.PathBuilderTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.SysPropsTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ColorTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ConfigTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.FileUtilTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.HostWhitelistTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.StringUtilTest.txt create mode 100644 getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.VersionUtilTest.txt create mode 100644 getdown/src/getdown/core/target/test-classes/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 getdown/src/getdown/launcher/.project create mode 100644 getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs create mode 100644 getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs create mode 100644 getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs create mode 100644 getdown/src/getdown/launcher/pom.xml create mode 100644 getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java create mode 100644 getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java create mode 100644 getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java create mode 100644 getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java create mode 100644 getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java create mode 100644 getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java create mode 100644 getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java create mode 100644 getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java create mode 100644 getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties create mode 100644 getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties create mode 100644 getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties create mode 100644 getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties create mode 100644 getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties create mode 100644 getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties create mode 100644 getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties create mode 100644 getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties create mode 100644 getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties create mode 100644 getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages.properties create mode 100644 getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_de.properties create mode 100644 getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_es.properties create mode 100644 getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_fr.properties create mode 100644 getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_it.properties create mode 100644 getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ja.properties create mode 100644 getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ko.properties create mode 100644 getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_pt.properties create mode 100644 getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_zh.properties create mode 100644 getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT.jar create mode 100644 getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT_proguard_base.jar create mode 100644 getdown/src/getdown/launcher/target/maven-archiver/pom.properties create mode 100644 getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 getdown/src/getdown/launcher/target/proguard_map.txt create mode 100644 getdown/src/getdown/launcher/target/proguard_seed.txt create mode 100644 getdown/src/getdown/lib/SOURCE_HEADER create mode 100644 getdown/src/getdown/lib/jRegistryKey.dll create mode 100644 getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.jar create mode 100644 getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.pom create mode 100644 getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml create mode 100644 getdown/src/getdown/lib/manifest.mf create mode 100644 getdown/src/getdown/pom.xml diff --git a/getdown/src/getdown b/getdown/src/getdown deleted file mode 160000 index 0e2f116..0000000 --- a/getdown/src/getdown +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e2f11698da3c6b9ffd9ce7a1317ff42b96a1dbf diff --git a/getdown/src/getdown/.project b/getdown/src/getdown/.project new file mode 100644 index 0000000..ccd1d40 --- /dev/null +++ b/getdown/src/getdown/.project @@ -0,0 +1,17 @@ + + + getdown + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/getdown/src/getdown/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/getdown/src/getdown/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/getdown/src/getdown/.travis.yml b/getdown/src/getdown/.travis.yml new file mode 100644 index 0000000..32f2196 --- /dev/null +++ b/getdown/src/getdown/.travis.yml @@ -0,0 +1,11 @@ +language: java +sudo: false +script: "mvn -B clean verify" + +cache: + directories: + - '$HOME/.m2/repository' + +jdk: + - openjdk7 + - oraclejdk8 diff --git a/getdown/src/getdown/AUTHORS b/getdown/src/getdown/AUTHORS new file mode 100644 index 0000000..5f389d0 --- /dev/null +++ b/getdown/src/getdown/AUTHORS @@ -0,0 +1,12 @@ +# +# This is the official list of the AUTHORS of Getdown for copyright purposes. +# +# This is not the full list of contributors, see +# https://github.com/threerings/getdown/graphs/contributors +# for the full list of contributors. +# +# Contributors assign copyright of their work to the authors listed in the this file to keep life +# simple. + +Michael Bayne +Ray Greenwell diff --git a/getdown/src/getdown/CHANGELOG.md b/getdown/src/getdown/CHANGELOG.md new file mode 100644 index 0000000..098651e --- /dev/null +++ b/getdown/src/getdown/CHANGELOG.md @@ -0,0 +1,180 @@ +# Getdown Releases + +## 1.8.3 - Unreleased + +* Added support for `nresource` resources which must be jar files that contain native libraries. + Prior to launching the application, these resources will be unpacked and their contents added to + the `java.library.path` system property. + +* When the app is updated to require a new version of the JVM, that JVM will be downloaded and used + immediately during that app invocation (instead of one invocation later). Via PR#169. + +* When a custom JVM is installed, old JVM files will be deleted prior to unpacking the new JVM. Via + PR#170. + +* Number of concurrent downloads now defaults to num-cores minus one. Though downloads are I/O + bound rather than CPU bound, this still turns out to be a decent default. + +* Avoid checking for proxy config if `https.proxyHost` is set. This matches existing behavior when + `http.proxyHost` is set. + +* Added support for proxy authentication. A deployment must also use the + `com.threerings.getdown.spi.ProxyAuth` service provider interface to persist the proxy + credentials supplied by the user. Otherwise they will be requested every time Getdown runs, which + is not a viable user experience. + +## 1.8.2 - Nov 27, 2018 + +* Fixed a data corruption bug introduced at last minute into 1.8.1 release. Oops. + +## 1.8.1 - Nov 26, 2018 + +* If both an `appbase` and `appdir` are provided via some means (bootstrap properties file, system + property, etc.) and the app dir does not yet exist, Getdown will create it. + +* Added `max_concurrent_downloads` setting to `getdown.txt`. Controls what you would expect. + Defaults to two. + +* `bootstrap.properties` can now contain system properties which will be set prior to running + Getdown. They must be prefixed by `sys.`: for example `sys.silent = true` will set the `silent` + system property to `true`. + +* If Getdown is run in a headless JVM, it will avoid showing a UI but will attempt to install and + launch the application anyhow. Note that passing `-Dsilent` will override this behavior (because + in silent mode the default is only to install the app, not also launch it). + +* Fixed issue with `appid` not being properly used when specified via command line arg. + +* Fixed issue with running Getdown on single CPU systems (or virtual systems). It was attempting to + create a thread pool of size zero, which failed. + +* Fixed issue with backslashes (or other regular expression escape characters) in environment + variables being substituted into app arguments. + +## 1.8.0 - Oct 19, 2018 + +* Added support for manually specifying the thread pool size via `-Dthread_pool_size`. Also reduced + the default thread pool size to `num_cpus-1` from `num_cpus`. + +* Added support for bundling a `bootstrap.properties` file with the Getdown jar file, which can + specify defaults for `appdir`, `appbase` and `appid`. + +* Added support for a host URL whitelist. Getdown can be custom built to refuse to operate with any + URL that does not match the built-time-specified whitelist. See `core/pom.xml` for details. + +* Removed the obsolete support for running Getdown in a signed applet. Applets are no longer + supported by any widely used browser. + +* Split the project into multiple Maven modules. See the notes on [migrating from 1.7 to 1.8] for + details. + +* A wide variety of small cleanups resulting from a security review generously performed by a + prospective user. This includes various uses of deterministic locales and encodings instead of + the platform default locale/encoding, in cases where platform/locale-specific behavior is not + desired or needed. + +* Made use of `appid` fall back to main app class if no `appid`-specific class is specified. + +* Added support for marking resources as executable (via `xresource`). + +* Fixed issue where entire tracking URL was being URL encoded. + +* Changed translations to avoid the use of the term 'game'. Use 'app' instead. + +## 1.7.1 - Jun 6, 2018 + +* Made it possible to use `appbase_domain` with `https` URLs. + +* Fixed issue with undecorated splash window being unclosable if failures happen early in + initialization process. (#57) + +* Added support for transparent splash window. (#92) + +* Fixed problem with unpacked code resources (`ucode`) and `pack.gz` files. (#95) + +* Changed default Java version regex to support new Java 9+ version formats. (#93) + +* Ensure correct signature algorithm is used for each version of digest files. (#91) + +* Use more robust delete in all cases where Getdown needs to delete files. This should fix issues + with lingering files on Windows (where sometimes delete fails spuriously). + +## 1.7.0 - Dec 12, 2017 + +* Fixed issue with `Digester` thread pool not being shutdown. (#89) + +* Fixed resource unpacking, which was broken by earlier change introducing resource installation + (downloading to `_new` files and then renaming into place). (#88) + +* The connect and read timeouts specified by system properties are now used for all the various + connections made by Getdown. + +* Proxy detection now uses a 5 second connect/read timeout, to avoid stalling for a long time in + certain problematic network conditions. + +* Getdown is now built against JDK 1.7 and requires JDK 1.7 (or newer) to run. Use the latest + Getdown 1.6.x release if you need to support Java 1.6. + +## 1.6.4 - Sep 17, 2017 + +* `digest.txt` (and `digest2.txt`) computation now uses parallel jobs. Each resource to be verified + is a single job and the jobs are doled out to a thread pool with #CPUs threads. This allows large + builds to proceed faster as most dev machines have more than one core. + +* Resource verification is now performed in parallel (similar to the `digest.txt` computation, each + resource is a job farmed out to a thread pool). For large installations on multi-core machines, + this speeds up the verification phase of an installation or update. + +* Socket reads now have a 30 second default timeout. This can be changed by passing + `-Dread_timeout=N` (where N is seconds) to the JVM running Getdown. + +* Fixed issue with failing to install a downloaded and validated `_new` file. + +* Added support for "strict comments". In this mode, Getdown only treats `#` as starting a comment + if it appears in column zero. This allows `#` to occur on the right hand side of configuration + values (like in file names). To enable, put `strict_comments = true` in your `getdown.txt` file. + +## 1.6.3 - Apr 23, 2017 + +* Fixed error parsing `cache_retention_days`. (#82) + +* Fixed error with new code cache. (9e23a426) + +## 1.6.2 - Feb 12, 2017 + +* Fixed issue with installing local JVM, caused by new resource installation process. (#78) + +* Local JVM now uses absolute path to avoid issues with cwd. + +* Added `override_appbase` system property. This enables a Getdown app that normally talks to some + download server to be installed in such a way that it instead talks to some other download + server. + +## 1.6.1 - Feb 12, 2017 + +* Fix issues with URL path encoding when downloading resources. (84af080b0) + +* Parsing `digest.txt` changed to allow `=` to appear in the filename. In `getdown.txt` we split on + the first `=` because `=` never appears in a key but may appear in a value. But in `digest.txt` + the format is `filename = hash` and `=` never appears in the hash but may appear in the filename, + so there we want to split on the _last_ `=` not the first. + +* Fixed bug with progress tracking and reporting. (256e0933) + +* Fix executable permissions on `jspawnhelper`. (#74) + +## 1.6 - Nov 5, 2016 + +* This release and all those before it are considered ancient history. Check the commit history for + more details on what was in each of these releases. + +## 1.0 - Sep 21, 2010 + +* The first Maven release of Getdown. + +## 0.1 - July 19, 2004 + +* The first production use of Getdown (on https://www.puzzlepirates.com which is miraculously still + operational as of 2018 when this changelog was created). + +[migrating from 1.7 to 1.8]: https://github.com/threerings/getdown/wiki/Migrate17to18 diff --git a/getdown/src/getdown/LICENSE b/getdown/src/getdown/LICENSE new file mode 100644 index 0000000..0d9b255 --- /dev/null +++ b/getdown/src/getdown/LICENSE @@ -0,0 +1,24 @@ +Getdown - application installer, patcher and launcher + +Copyright (C) 2004-2016 Getdown authors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. diff --git a/getdown/src/getdown/README.md b/getdown/src/getdown/README.md new file mode 100644 index 0000000..7059c61 --- /dev/null +++ b/getdown/src/getdown/README.md @@ -0,0 +1,111 @@ +## What is it? + +Getdown (yes, it's the funky stuff) is a system for deploying Java applications to end-user +computers, as well as keeping those applications up to date. + +It was designed as a replacement for [Java Web Start](https://docs.oracle.com/javase/8/docs/technotes/guides/javaws/) +due to limitations in Java Web Start's architecture which are outlined in the +[rationale](https://github.com/threerings/getdown/wiki/Rationale) section. + +Note: Getdown was designed *in 2004* as an alternative to Java Web Start, because of design choices +made by JWS that were problematic to the use cases its authors had. It is _not_ a drop-in +replacement for JWS, aimed to help the developers left in the lurch by the deprecation of JWS in +Java 9. It may still be a viable alternative for developers looking to replace JWS, but don't +expect to find feature parity with JWS. + +## How do I use it? + +A tutorial and more detailed specification are available from the [Documentation] page. Questions +can be posted to the [OOO Libs Google group]. + +Note that because one can not rely on users having a JRE installed, you must create a custom +installer for each platform that you plan to support (Windows, macOS, Linux) that installs a JRE, +the Getdown launcher jar file, a stub configuration file that identifies the URL at which your real +app manifest is hosted, and whatever the appropiate "desktop integration" is that provides an icon +the user can click on. We have some details on the +[installers](https://github.com/threerings/getdown/wiki/Installers) documentation page, though it +is unfortunately not very detailed. + +## How does it work? + +The main design and operation of Getdown is detailed on the +[design](https://github.com/threerings/getdown/wiki/Design) page. You can also browse the +[javadoc documentation] and [source code] if you're interested in implementation details. + +## Where can I see it in action? + +Getdown was originally written by developers at [OOO] for the deployment of their Java-based +massively multiplayer games. Try out any of the following games to see it in action: + + * [Puzzle Pirates](https://www.puzzlepirates.com/) - OOO + * [Spiral Knights](https://www.spiralknights.com/) - OOO + +Getdown is implemented in Java, and is designed to deploy and update JVM-based applications. While +it would be technically feasible to use Getdown to deploy non-JVM-based applications, it is not +currently supported and it is unlikely that the overhead of bundling a JVM just to run Getdown +would be worth it if the JVM were not also being used to run the target application. + +## Release notes + +See [CHANGELOG.md](CHANGELOG.md) for release notes. + +## Obtaining Getdown + +Getdown will likely need to be integrated into your build. We have separate instructions for +[build integration]. You can also download the individual jar files from Maven Central if needed. +Getdown is comprised of three Maven artifacts (jar files), though you probably only need the first +one: + + * [getdown-launcher](http://repo2.maven.org/maven2/com/threerings/getdown/getdown-launcher) + contains minified (via Proguard) code that you actually run to update and launch your app. It + also contains the tools needed to build a Getdown app distribution. + + * [getdown-core](http://repo2.maven.org/maven2/com/threerings/getdown/getdown-core) contains the + core logic for downloading, verifying, patching and launching an app as well as the core logic + for creating an app distribution. It does not contain any user interface code. You would only + use this artifact if you were planning to integrate Getdown directly into your app. + + * [getdown-ant](http://repo2.maven.org/maven2/com/threerings/getdown/getdown-ant) contains an Ant + task for building a Getdown app distribution. See the [build integration] instructions for + details. + +You can also: + + * [Check out the code](https://github.com/threerings/getdown) and build it yourself. + * Browse the [source code] online. + * View the [javadoc documentation] online. + +## JVM Version Requirements + + * Getdown version 1.8.x requires Java 7 VM or newer. + * Getdown version 1.7.x requires Java 7 VM or newer. + * Getdown version 1.6.x requires Java 6 VM or newer. + * Getdown version 1.5 and earlier requires Java 5 VM or newer. + +## Migrating from Getdown 1.7 to Getdown 1.8 + +See [this document](https://github.com/threerings/getdown/wiki/Migrating-from-1.7-to-1.8) on the +changes needed to migrate from Getdown 1.7 to 1.8. + +## Building + +Getdown is built with Maven in the standard ways. Invoke the following commands, for fun and +profit: + +``` +% mvn compile # builds the classes +% mvn test # builds and runs the unit tests +% mvn package # builds and creates jar file +% mvn install # builds, jars and installs in your local Maven repository +``` + +## Discussion + +Feel free to pop over to the [OOO Libs Google Group] to ask questions and get (and give) answers. + +[Documentation]: https://github.com/threerings/getdown/wiki +[OOO Libs Google group]: http://groups.google.com/group/ooo-libs +[source code]: https://github.com/threerings/getdown/tree/master/src/main/java/com/threerings/getdown/launcher +[javadoc documentation]: https://threerings.github.com/getdown/apidocs/ +[OOO]: https://en.wikipedia.org/wiki/Three_Rings_Design +[build integration]: https://github.com/threerings/getdown/wiki/Build-Integration diff --git a/getdown/src/getdown/ant/.project b/getdown/src/getdown/ant/.project new file mode 100644 index 0000000..097cb89 --- /dev/null +++ b/getdown/src/getdown/ant/.project @@ -0,0 +1,23 @@ + + + getdown-ant + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..e9441bb --- /dev/null +++ b/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding/=UTF-8 diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..54e5672 --- /dev/null +++ b/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/getdown/src/getdown/ant/pom.xml b/getdown/src/getdown/ant/pom.xml new file mode 100644 index 0000000..f8231aa --- /dev/null +++ b/getdown/src/getdown/ant/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + com.threerings.getdown + getdown + 1.8.3-SNAPSHOT + + + getdown-ant + jar + Getdown Ant Task + An Ant task for building Getdown app distributions + + + + com.threerings.getdown + getdown-core + ${project.version} + + + org.apache.ant + ant + 1.7.1 + provided + + + diff --git a/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java b/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java new file mode 100644 index 0000000..48cc8d4 --- /dev/null +++ b/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java @@ -0,0 +1,94 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +import java.io.File; +import java.io.IOException; + +import java.security.GeneralSecurityException; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +import com.threerings.getdown.data.Digest; + +/** + * An ant task used to create a digest.txt for a Getdown + * application deployment. + */ +public class DigesterTask extends Task +{ + /** + * Sets the application directory. + */ + public void setAppdir (File appdir) + { + _appdir = appdir; + } + + /** + * Sets the digest signing keystore. + */ + public void setKeystore (File path) + { + _storepath = path; + } + + /** + * Sets the keystore decryption key. + */ + public void setStorepass (String password) + { + _storepass = password; + } + + /** + * Sets the private key alias. + */ + public void setAlias (String alias) + { + _storealias = alias; + } + + /** + * Performs the actual work of the task. + */ + @Override + public void execute () throws BuildException + { + // make sure appdir is set + if (_appdir == null) { + throw new BuildException("Must specify the path to the application directory " + + "via the 'appdir' attribute."); + } + + // make sure _storepass and _keyalias are set, if _storepath is set + if (_storepath != null && (_storepass == null || _storealias == null)) { + throw new BuildException( + "Must specify both a keystore password and a private key alias."); + } + + try { + Digester.createDigests(_appdir, _storepath, _storepass, _storealias); + } catch (IOException ioe) { + throw new BuildException("Error creating digest: " + ioe.getMessage(), ioe); + } catch (GeneralSecurityException gse) { + throw new BuildException("Error creating signature: " + gse.getMessage(), gse); + } + } + + /** The application directory in which we're creating a digest file. */ + protected File _appdir; + + /** The path to the keystore we'll use to sign the digest file, if any. */ + protected File _storepath; + + /** The decryption key for the keystore. */ + protected String _storepass; + + /** The private key alias. */ + protected String _storealias; +} diff --git a/getdown/src/getdown/ant/target/getdown-ant-1.8.3-SNAPSHOT.jar b/getdown/src/getdown/ant/target/getdown-ant-1.8.3-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..ff501eb7d03988279917ac035ae839bf4dea5d65 GIT binary patch literal 3331 zcmbW42{@E%8^_=5%M4;9mnb2FnX#7Qh?H#>%b++U+oZ`hW(YG;QnF+xTXF2WqmpH; zZRl9Tmm@?za#Tj7MC8m=8Xr05ySnFk-}kw$x$pmTKkxfKzxj_Tij|EAV8jyPit(4h zw-Y!0iqW$;0yi?&S2X>W#sw_W)2wNy#69R=U!fn6^=X(R#zy*j<`xKy{yR*28yW>i zJOQKN@}2Encd$x*s-qJ`7_ze!hJx#IagOI4PuQW*B<)vo1F1?vdf${Ug~Wxw6N;UL?$MDukh4E?BFuU`vSiFq0uO${ucn>bm4eUgZCCT0LSL}R_mw$v zO<9({PUkDl$I(|}w4RBpHrCh!)8RvxnVNCl{CG1-=V&4EJ)=bjL$h-0$Cg2+By*P7P zfOdiTiD?np!6fO@u%!DQC`U}~;k((gW+f$x4HbGQYJ)*$-p!c0$2%n9+_wJqa9$I( zI%`z(O=izMOd{kW*B1>ld`_vIWx6Gd0WzD;C#qJ>VSV$1R*MFL-&z8NQ z265ZER8Mh6J+c|{2t8&^GkWbc=>F8?fW1|5rO|6flL{TafwB5us4CXx?ZkOG8X(oWZ zPfJkZftZK3ew6looD9!Xr6r@v^J3c;e$OZc0x0&RDGi;w5s9*@>aF(ps zHM@4uO2NbI=YEcb-BOy{2Vlum?!HvD+pQ|M10G40_xBlXw*;}61)@v0vl4_dVq|8O z6JhDH-pI^|TL$0ySz) z><(MW7XRE2X?I`pKs-^$4ouElqYWM>;yL^PFn?^zUZciip_cc5$+cr|D-a}u`JT=y zXlWyX_@@Z-qN-U@nQ7A*(o$e?kAGt5oXpDAn2zXq?OuY5&4XdFkMc$69l}HXvDG8X z{7md^q&EX<-|6k{a1OVgPaBi>ZBC{m6Tt)`Vl(45YN-6*8sD_w4j$g$naKr|`3D9e#&?N{>Kq+t1xC5qr{PPze&y^?P|%eY5d4d;u|XNk!*UP@l>)SadKa zq`A^feM)V*v1>O*n9BFihE_BA{08Ox+eeirgN?0Z@1`aSStaQ}^J*+QO3qsa7I(Js zV(p#YN7zfSRTA$IC+bu7_VpM;4Z%2tZFk+fSBBvl!9GKA$-SlVlyf5s<=U9Kcxw7@ z8CB{LN`Lks}SsG0;a|M5pjKr7Pc8!Mhwv zX(IXk3UBC|5jo+Dx<-xVPz!}dd6X8+T}=!2_<&5?6J2L2rVhfn$v@*ErsUtV^)Jt% z31_pl92SYP#%E1Cr;8p|YPwT7ikIqF7Pe2$Zw3%ItLE@Rvb*&EySP_TO?OrRDmO%X zt2M8{nsIlbS{Q9J`+J*IM&t5xy`8ieZO^(WkKOVVle32X0u}Q-Glsv+Avw7}^2l;J z>JBQqvvFr>K4x!$_8a6zy&5O^eK@Zm_nBqw-R1h?Jd|?;Q^@p3et~}I{Pd$I5IqSv zqBkB#g8!k(cV7tvbZh08_tluOSm@;P;YL;8#`f@DZU&1Iv=vj`>e2aaeumQ?_~aU zPAOv6!KqNexwHfqqu`kd_0JY$!T0Q@sBJ6|)~!7ieR1><0H%*HuHW}4v+Tzni;>KT zoFG~H`Fq(q$Hi3G>~R<_=xZ=0ZlMpqdLm}48+{W4AYX$~F-0FTDws&1Z{4kn87}E- zFp783xAcZ$CQr5&VYnuy2x@DQH4n8W^f!?nn0IaUQ-dwyTh_}{cpqQzt$g!!REj0fZ%xn5TM)TSXIYAeGO8* + + getdown-core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..0a9bbb8 --- /dev/null +++ b/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/it/java=UTF-8 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..54e5672 --- /dev/null +++ b/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/getdown/src/getdown/core/pom.xml b/getdown/src/getdown/core/pom.xml new file mode 100644 index 0000000..e564bf6 --- /dev/null +++ b/getdown/src/getdown/core/pom.xml @@ -0,0 +1,132 @@ + + + 4.0.0 + + com.threerings.getdown + getdown + 1.8.3-SNAPSHOT + + + getdown-core + jar + Getdown Core + Core Getdown functionality + + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.22.0 + test + + + + + + + + + + + + .. + LICENSE + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.5 + + + add-test-source + process-resources + + add-test-source + + + + src/it/java + + + + + + + + maven-antrun-plugin + 1.8 + + + gen-build + generate-sources + + + + + + + + + + + + + + + + run + + + + + + + maven-clean-plugin + 3.1.0 + + + + ${project.build.sourceDirectory}/ + + com/threerings/getdown/data/Build.java + + false + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.22.0 + + + + integration-test + verify + + + + + false + + + + + + diff --git a/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java b/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java new file mode 100644 index 0000000..52b4b5e --- /dev/null +++ b/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java @@ -0,0 +1,54 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tests; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.util.Arrays; +import java.util.List; + +import org.junit.*; +import static org.junit.Assert.*; + +import com.threerings.getdown.tools.Digester; + +public class DigesterIT { + + @Test + public void testDigester () throws Exception { + Path appdir = Paths.get("src/it/resources/testapp"); + Digester.createDigests(appdir.toFile(), null, null, null); + + Path digest = appdir.resolve("digest.txt"); + List digestLines = Files.readAllLines(digest, StandardCharsets.UTF_8); + Files.delete(digest); + + Path digest2 = appdir.resolve("digest2.txt"); + List digest2Lines = Files.readAllLines(digest2, StandardCharsets.UTF_8); + Files.delete(digest2); + + assertEquals(Arrays.asList( + "getdown.txt = 779c74fb4b251e18faf6e240a0667964", + "testapp.jar = 404dafa55e78b25ec0e3a936357b1883", + "funny%test dir/some=file.txt = d8e8fca2dc0f896fd7cb4cb0031ba249", + "crazyhashfile#txt = f29d23fd5ab1781bd8d0760b3a516f16", + "foo.jar = 46ca4cc9079d9d019bb30cd21ebbc1ec", + "script.sh = f66e8ea25598e67e99c47d9b0b2a2cdf", + "digest.txt = f5561d85e4d80cc85883963897e58ff6" + ), digestLines); + + assertEquals(Arrays.asList( + "getdown.txt = 4f0c657895c3c3a35fa55bf5951c64fa9b0694f8fc685af3f1d8635c639e066b", + "testapp.jar = c9cb1906afbf48f8654b416c3f831046bd3752a76137e5bf0a9af2f790bf48e0", + "funny%test dir/some=file.txt = f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2", + "crazyhashfile#txt = 6816889f922de38f145db215a28ad7c5e1badf7354b5cdab225a27486789fa3b", + "foo.jar = ea188b872e0496debcbe00aaadccccb12a8aa9b025bb62c130cd3d9b8540b062", + "script.sh = cca1c5c7628d9bf7533f655a9cfa6573d64afb8375f81960d1d832dc5135c988", + "digest2.txt = 70b442c9f56660561921da3368e1a206f05c379182fab3062750b7ddcf303407" + ), digest2Lines); + } +} diff --git a/getdown/src/getdown/core/src/it/resources/testapp/background.png b/getdown/src/getdown/core/src/it/resources/testapp/background.png new file mode 100644 index 0000000000000000000000000000000000000000..ff6a6eea907f069c76f218559a23c7ebadf13a58 GIT binary patch literal 32808 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 WG~5EbSwVrtzzT#)ASobbU;qFYmw1u@ literal 0 HcmV?d00001 diff --git a/getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt b/getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt @@ -0,0 +1 @@ +test diff --git a/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt b/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt new file mode 100644 index 0000000..3e0e538 --- /dev/null +++ b/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt @@ -0,0 +1,28 @@ +# where our app is hosted on the internets +appbase = http://notused.com/testapp + +# the jar file that contains our code +code = testapp.jar + +# the main entry point of our app +class = com.threerings.testapp.TestApp + +# we pass the appdir to our app so that it can upgrade getdown +apparg = %APPDIR% + +# test the %env% mechanism +jvmarg = -Dusername=\%ENV.USER% + +strict_comments = true +resource = funny%test dir/some=file.txt +resource = crazyhashfile#txt +uresource = foo.jar +xresource = script.sh + +ui.name = Getdown Test App +ui.background_image = background.png +ui.progress = 17, 321, 458, 22 +ui.progress_bar = 336600 +ui.progress_text = FFFFFF +ui.status = 57, 245, 373, 68 +ui.status_text = 000000 diff --git a/getdown/src/getdown/core/src/it/resources/testapp/script.sh b/getdown/src/getdown/core/src/it/resources/testapp/script.sh new file mode 100644 index 0000000..e3a1aba --- /dev/null +++ b/getdown/src/getdown/core/src/it/resources/testapp/script.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Hello world!" diff --git a/getdown/src/getdown/core/src/it/resources/testapp/testapp.jar b/getdown/src/getdown/core/src/it/resources/testapp/testapp.jar new file mode 100644 index 0000000000000000000000000000000000000000..fe9de0201844514c70683cd4a7c9491f44b515cf GIT binary patch literal 823 zcmWIWW@Zs#;Nak3_)w$j%YXzp8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gvRs&ev7IF;+x0>duZ?+nlXcPqw72`VqM+px`0v@%GG1?N=?>R^|jtl?+2UtuH}~7CYj}(z@}{$=OBE2Zz}WRR-3n1 z=J3pzIpLmArSGPmoPRHv-%PdJB2~q0Y~Vdb)S~#+K_9Lu_a>}NyFW4dLM~Sz->IyY z)tj}l_k$8e*9q2h_KXY+=YcrDn~_O`0X0p*5)3F!p#pd!0i`f>t;q2VN>K=43uMBz nBBd{66F}(!* 0) { + buf.append(' '); + } + buf.append('['); + for (int ii = 0; ii < args.length; ii += 2) { + if (ii > 0) { + buf.append(',').append(' '); + } + buf.append(args[ii]).append('='); + try { + buf.append(args[ii+1]); + } catch (Throwable t) { + buf.append(""); + } + } + return buf.append(']').toString(); + } + + static { + Formatter formatter = new OneLineFormatter(); + Logger logger = LogManager.getLogManager().getLogger(""); + for (Handler handler : logger.getHandlers()) { + handler.setFormatter(formatter); + } + } + + protected static class OneLineFormatter extends Formatter { + @Override public String format (LogRecord record) { + StringBuffer buf = new StringBuffer(); + + // append the timestamp + _date.setTime(record.getMillis()); + _format.format(_date, buf, _fpos); + + // append the log level + buf.append(" "); + buf.append(record.getLevel().getLocalizedName()); + buf.append(" "); + + // append the message itself + buf.append(formatMessage(record)); + buf.append(System.lineSeparator()); + + // if an exception was also provided, append that + if (record.getThrown() != null) { + try { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + record.getThrown().printStackTrace(pw); + pw.close(); + buf.append(sw.toString()); + } catch (Exception ex) { + buf.append("Format failure:").append(ex); + } + } + + return buf.toString(); + } + + protected Date _date = new Date(); + protected SimpleDateFormat _format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss:SSS"); + protected FieldPosition _fpos = new FieldPosition(SimpleDateFormat.DATE_FIELD); + } + + protected static final String DATE_FORMAT = "{0,date} {0,time}"; + protected static final Level[] LEVELS = {Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE}; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java new file mode 100644 index 0000000..67ea645 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java @@ -0,0 +1,99 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.cache; + +import java.io.File; +import com.threerings.getdown.util.FileUtil; + +/** + * Collects elements in the {@link ResourceCache cache} which became unused and deletes them + * afterwards. + */ +public class GarbageCollector +{ + /** + * Collect and delete the garbage in the cache. + */ + public static void collect (File cacheDir, final long retentionPeriodMillis) + { + FileUtil.walkTree(cacheDir, new FileUtil.Visitor() { + @Override public void visit (File file) { + File cachedFile = getCachedFile(file); + File lastAccessedFile = getLastAccessedFile(file); + if (!cachedFile.exists() || !lastAccessedFile.exists()) { + if (cachedFile.exists()) { + FileUtil.deleteHarder(cachedFile); + } else { + FileUtil.deleteHarder(lastAccessedFile); + } + } else if (shouldDelete(lastAccessedFile, retentionPeriodMillis)) { + FileUtil.deleteHarder(lastAccessedFile); + FileUtil.deleteHarder(cachedFile); + } + + File folder = file.getParentFile(); + if (folder != null) { + String[] children = folder.list(); + if (children != null && children.length == 0) { + FileUtil.deleteHarder(folder); + } + } + } + }); + } + + /** + * Collect and delete garbage in the native cache. It tries to find a jar file with a matching + * last modified file, and deletes the entire directory accordingly. + */ + public static void collectNative (File cacheDir, final long retentionPeriodMillis) + { + File[] subdirs = cacheDir.listFiles(); + if (subdirs != null) { + for (File dir : subdirs) { + if (dir.isDirectory()) { + // Get all the native jars in the directory (there should only be one) + for (File file : dir.listFiles()) { + if (!file.getName().endsWith(".jar")) { + continue; + } + File cachedFile = getCachedFile(file); + File lastAccessedFile = getLastAccessedFile(file); + if (!cachedFile.exists() || !lastAccessedFile.exists() || + shouldDelete(lastAccessedFile, retentionPeriodMillis)) { + FileUtil.deleteDirHarder(dir); + } + } + } else { + // @TODO There shouldn't be any loose files in native/ but if there are then + // what? Delete them? file.delete(); + } + } + } + } + + private static boolean shouldDelete (File lastAccessedFile, long retentionMillis) + { + return System.currentTimeMillis() - lastAccessedFile.lastModified() > retentionMillis; + } + + private static File getLastAccessedFile (File file) + { + return isLastAccessedFile(file) ? file : new File( + file.getParentFile(), file.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX); + } + + private static boolean isLastAccessedFile (File file) + { + return file.getName().endsWith(ResourceCache.LAST_ACCESSED_FILE_SUFFIX); + } + + private static File getCachedFile (File file) + { + return !isLastAccessedFile(file) ? file : new File( + file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf("."))); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java new file mode 100644 index 0000000..0210e9a --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java @@ -0,0 +1,80 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.cache; + +import java.io.File; +import java.io.IOException; + +import com.threerings.getdown.util.FileUtil; + +/** + * Maintains a cache of code resources. The cache allows multiple application instances of different + * versions to open at the same time. + */ +public class ResourceCache +{ + public ResourceCache (File _cacheDir) throws IOException + { + this._cacheDir = _cacheDir; + createDirectoryIfNecessary(_cacheDir); + } + + private void createDirectoryIfNecessary (File dir) throws IOException + { + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException("unable to create directory: " + dir.getAbsolutePath()); + } + } + + /** + * Caches the given file under its {@code digest}. + * @param fileToCache file to cache. + * @param cacheSubdir the subdirectory of the cache directory in which to store the cached + * file. Usually either {@code digest} or a prefix of {@code digest}. + * @param digest a crypto digest of the cached files contents. + * @return the cached file. + */ + public File cacheFile (File fileToCache, String cacheSubdir, String digest) throws IOException + { + File cacheLocation = new File(_cacheDir, cacheSubdir); + createDirectoryIfNecessary(cacheLocation); + + File cachedFile = new File(cacheLocation, digest + getFileSuffix(fileToCache)); + File lastAccessedFile = new File( + cacheLocation, cachedFile.getName() + LAST_ACCESSED_FILE_SUFFIX); + + if (!cachedFile.exists()) { + createNewFile(cachedFile); + FileUtil.copy(fileToCache, cachedFile); + } + + if (lastAccessedFile.exists()) { + lastAccessedFile.setLastModified(System.currentTimeMillis()); + } else { + createNewFile(lastAccessedFile); + } + + return cachedFile; + } + + private void createNewFile (File fileToCreate) throws IOException + { + if (!fileToCreate.exists() && !fileToCreate.createNewFile()) { + throw new IOException("unable to create new file: " + fileToCreate.getAbsolutePath()); + } + } + + private String getFileSuffix (File fileToCache) { + String fileName = fileToCache.getName(); + int index = fileName.lastIndexOf("."); + + return index > -1 ? fileName.substring(index) : ""; + } + + private final File _cacheDir; + + static final String LAST_ACCESSED_FILE_SUFFIX = ".lastAccessed"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java new file mode 100644 index 0000000..501407c --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java @@ -0,0 +1,1826 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.*; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.security.*; +import java.security.cert.Certificate; +import java.util.*; +import java.util.concurrent.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; +import com.sun.management.OperatingSystemMXBean; +import java.lang.management.ManagementFactory; + + +import com.threerings.getdown.util.*; +// avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+ +import com.threerings.getdown.util.Base64; + +import static com.threerings.getdown.Log.log; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Parses and provide access to the information contained in the getdown.txt + * configuration file. + */ +public class Application +{ + /** The name of our configuration file. */ + public static final String CONFIG_FILE = "getdown.txt"; + + /** The name of our target version file. */ + public static final String VERSION_FILE = "version.txt"; + + /** System properties that are prefixed with this string will be passed through to our + * application (minus this prefix). */ + public static final String PROP_PASSTHROUGH_PREFIX = "app."; + + /** Suffix used for control file signatures. */ + public static final String SIGNATURE_SUFFIX = ".sig"; + + /** A special classname that means 'use -jar code.jar' instead of a classname. */ + public static final String MANIFEST_CLASS = "manifest"; + + /** Used to communicate information about the UI displayed when updating the application. */ + public static final class UpdateInterface + { + /** + * The major steps involved in updating, along with some arbitrary percentages + * assigned to them, to mark global progress. + */ + public enum Step + { + UPDATE_JAVA(10), + VERIFY_METADATA(15, 65, 95), + DOWNLOAD(40), + PATCH(60), + VERIFY_RESOURCES(70, 97), + REDOWNLOAD_RESOURCES(90), + UNPACK(98), + LAUNCH(99); + + /** What is the final percent value for this step? */ + public final List defaultPercents; + + /** Enum constructor. */ + Step (int... percents) + { + this.defaultPercents = intsToList(percents); + } + } + + /** The human readable name of this application. */ + public final String name; + + /** A background color, just in case. */ + public final int background; + + /** Background image specifiers for `RotatingBackgrounds`. */ + public final List rotatingBackgrounds; + + /** The error background image for `RotatingBackgrounds`. */ + public final String errorBackground; + + /** The paths (relative to the appdir) of images for the window icon. */ + public final List iconImages; + + /** The path (relative to the appdir) to a single background image. */ + public final String backgroundImage; + + /** The path (relative to the appdir) to the progress bar image. */ + public final String progressImage; + + /** The dimensions of the progress bar. */ + public final Rectangle progress; + + /** The color of the progress text. */ + public final int progressText; + + /** The color of the progress bar. */ + public final int progressBar; + + /** The dimensions of the status display. */ + public final Rectangle status; + + /** The color of the status text. */ + public final int statusText; + + /** The color of the text shadow. */ + public final int textShadow; + + /** Where to point the user for help with install errors. */ + public final String installError; + + /** The dimensions of the patch notes button. */ + public final Rectangle patchNotes; + + /** The patch notes URL. */ + public final String patchNotesUrl; + + /** Whether window decorations are hidden for the UI. */ + public final boolean hideDecorations; + + /** Whether progress text should be hidden or not. */ + public final boolean hideProgressText; + + /** The minimum number of seconds to display the GUI. This is to prevent the GUI from + * flashing up on the screen and immediately disappearing, which can be confusing to the + * user. */ + public final int minShowSeconds; + + /** The global percentages for each step. A step may have more than one, and + * the lowest reasonable one is used if a step is revisited. */ + public final Map> stepPercentages; + + /** Generates a string representation of this instance. */ + @Override + public String toString () + { + return "[name=" + name + ", bg=" + background + ", bg=" + backgroundImage + + ", pi=" + progressImage + ", prect=" + progress + ", pt=" + progressText + + ", pb=" + progressBar + ", srect=" + status + ", st=" + statusText + + ", shadow=" + textShadow + ", err=" + installError + ", nrect=" + patchNotes + + ", notes=" + patchNotesUrl + ", stepPercentages=" + stepPercentages + + ", hideProgressText" + hideProgressText + ", minShow=" + minShowSeconds + "]"; + } + + public UpdateInterface (Config config) + { + this.name = config.getString("ui.name"); + this.progress = config.getRect("ui.progress", new Rectangle(5, 5, 300, 15)); + this.progressText = config.getColor("ui.progress_text", Color.BLACK); + this.hideProgressText = config.getBoolean("ui.hide_progress_text"); + this.minShowSeconds = config.getInt("ui.min_show_seconds", 5); + this.progressBar = config.getColor("ui.progress_bar", 0x6699CC); + this.status = config.getRect("ui.status", new Rectangle(5, 25, 500, 100)); + this.statusText = config.getColor("ui.status_text", Color.BLACK); + this.textShadow = config.getColor("ui.text_shadow", Color.CLEAR); + this.hideDecorations = config.getBoolean("ui.hide_decorations"); + this.backgroundImage = config.getString("ui.background_image"); + // default to black or white bg color, depending on the brightness of the progressText + int defaultBackground = (0.5f < Color.brightness(this.progressText)) ? + Color.BLACK : Color.WHITE; + this.background = config.getColor("ui.background", defaultBackground); + this.progressImage = config.getString("ui.progress_image"); + this.rotatingBackgrounds = stringsToList( + config.getMultiValue("ui.rotating_background")); + this.iconImages = stringsToList(config.getMultiValue("ui.icon")); + this.errorBackground = config.getString("ui.error_background"); + + // On an installation error, where do we point the user. + String installError = config.getUrl("ui.install_error", null); + this.installError = (installError == null) ? + "m.default_install_error" : MessageUtil.taint(installError); + + // the patch notes bits + this.patchNotes = config.getRect("ui.patch_notes", new Rectangle(5, 50, 112, 26)); + this.patchNotesUrl = config.getUrl("ui.patch_notes_url", null); + + // step progress percentage (defaults and then customized values) + EnumMap> stepPercentages = new EnumMap<>(Step.class); + for (Step step : Step.values()) { + stepPercentages.put(step, step.defaultPercents); + } + for (UpdateInterface.Step step : UpdateInterface.Step.values()) { + String spec = config.getString("ui.percents." + step.name()); + if (spec != null) { + try { + stepPercentages.put(step, intsToList(StringUtil.parseIntArray(spec))); + } catch (Exception e) { + log.warning("Failed to parse percentages for " + step + ": " + spec); + } + } + } + this.stepPercentages = Collections.unmodifiableMap(stepPercentages); + } + } + + /** + * Used by {@link #verifyMetadata} to communicate status in circumstances where it needs to + * take network actions. + */ + public static interface StatusDisplay + { + /** Requests that the specified status message be displayed. */ + public void updateStatus (String message); + } + + /** + * Contains metadata for an auxiliary resource group. + */ + public static class AuxGroup { + public final String name; + public final List codes; + public final List rsrcs; + + public AuxGroup (String name, List codes, List rsrcs) { + this.name = name; + this.codes = Collections.unmodifiableList(codes); + this.rsrcs = Collections.unmodifiableList(rsrcs); + } + } + + /** The proxy that should be used to do HTTP downloads. This must be configured prior to using + * the application instance. Yes this is a public mutable field, no I'm not going to create a + * getter and setter just to pretend like that's not the case. */ + public Proxy proxy = Proxy.NO_PROXY; + + /** + * Creates an application instance which records the location of the getdown.txt + * configuration file from the supplied application directory. + * + */ + public Application (EnvConfig envc) { + _envc = envc; + _config = getLocalPath(envc.appDir, CONFIG_FILE); + } + + /** + * Returns the configured application directory. + */ + public File getAppDir () { + return _envc.appDir; + } + + /** + * Returns whether the application should cache code resources prior to launching the + * application. + */ + public boolean useCodeCache () + { + return _useCodeCache; + } + + /** + * Returns the number of days a cached code resource is allowed to stay unused before it + * becomes eligible for deletion. + */ + public int getCodeCacheRetentionDays () + { + return _codeCacheRetentionDays; + } + + /** + * Returns the configured maximum concurrent downloads. Used to cap simultaneous downloads of + * app files from its hosting server. + */ + public int maxConcurrentDownloads () { + return _maxConcDownloads; + } + + /** + * Returns a resource that refers to the application configuration file itself. + */ + public Resource getConfigResource () + { + try { + return createResource(CONFIG_FILE, Resource.NORMAL); + } catch (Exception e) { + throw new RuntimeException("Invalid appbase '" + _vappbase + "'.", e); + } + } + + /** + * Returns a list of the code {@link Resource} objects used by this application. + */ + public List getCodeResources () + { + return _codes; + } + + /** + * Returns a list of the non-code {@link Resource} objects used by this application. + */ + public List getResources () + { + return _resources; + } + + /** + * Returns the digest of the given {@code resource}. + */ + public String getDigest (Resource resource) + { + return _digest.getDigest(resource); + } + + /** + * Returns a list of all the active {@link Resource} objects used by this application (code and + * non-code). + */ + public List getAllActiveResources () + { + List allResources = new ArrayList<>(); + allResources.addAll(getActiveCodeResources()); + allResources.addAll(getActiveResources()); + return allResources; + } + + /** + * Returns the auxiliary resource group with the specified name, or null. + */ + public AuxGroup getAuxGroup (String name) + { + return _auxgroups.get(name); + } + + /** + * Returns the set of all auxiliary resource groups defined by the application. An auxiliary + * resource group is a collection of resource files that are not downloaded unless a group + * token file is present in the application directory. + */ + public Iterable getAuxGroups () + { + return _auxgroups.values(); + } + + /** + * Returns true if the specified auxgroup has been "activated", false if not. Non-activated + * groups should be ignored, activated groups should be downloaded and patched along with the + * main resources. + */ + public boolean isAuxGroupActive (String auxgroup) + { + Boolean active = _auxactive.get(auxgroup); + if (active == null) { + // TODO: compare the contents with the MD5 hash of the auxgroup name and the client's + // machine ident + active = getLocalPath(auxgroup + ".dat").exists(); + _auxactive.put(auxgroup, active); + } + return active; + } + + /** + * Returns all main code resources and all code resources from active auxiliary resource groups. + */ + public List getActiveCodeResources () + { + ArrayList codes = new ArrayList<>(); + codes.addAll(getCodeResources()); + for (AuxGroup aux : getAuxGroups()) { + if (isAuxGroupActive(aux.name)) { + codes.addAll(aux.codes); + } + } + return codes; + } + + /** + * Returns all resources indicated to contain native library files (.dll, .so, etc.). + */ + public List getNativeResources () + { + List natives = new ArrayList<>(); + for (Resource resource: _resources) { + if (resource.isNative()) { + natives.add(resource); + } + } + return natives; + } + + /** + * Returns all non-code resources and all resources from active auxiliary resource groups. + */ + public List getActiveResources () + { + ArrayList rsrcs = new ArrayList<>(); + rsrcs.addAll(getResources()); + for (AuxGroup aux : getAuxGroups()) { + if (isAuxGroupActive(aux.name)) { + rsrcs.addAll(aux.rsrcs); + } + } + return rsrcs; + } + + /** + * Returns a resource that can be used to download a patch file that will bring this + * application from its current version to the target version. + * + * @param auxgroup the auxiliary resource group for which a patch resource is desired or null + * for the main application patch resource. + */ + public Resource getPatchResource (String auxgroup) + { + if (_targetVersion <= _version) { + log.warning("Requested patch resource for up-to-date or non-versioned application", + "cvers", _version, "tvers", _targetVersion); + return null; + } + + String infix = (auxgroup == null) ? "" : ("-" + auxgroup); + String pfile = "patch" + infix + _version + ".dat"; + try { + URL remote = new URL(createVAppBase(_targetVersion), encodePath(pfile)); + return new Resource(pfile, remote, getLocalPath(pfile), Resource.NORMAL); + } catch (Exception e) { + log.warning("Failed to create patch resource path", + "pfile", pfile, "appbase", _appbase, "tvers", _targetVersion, "error", e); + return null; + } + } + + /** + * Returns a resource for a zip file containing a Java VM that can be downloaded to use in + * place of the installed VM (in the case where the VM that launched Getdown does not meet the + * application's version requirements) or null if no VM is available for this platform. + */ + public Resource getJavaVMResource () + { + if (StringUtil.isBlank(_javaLocation)) { + return null; + } + + String vmfile = LaunchUtil.LOCAL_JAVA_DIR + ".jar"; + log.info("vmfile is '"+vmfile+"'"); + System.out.println("vmfile is '"+vmfile+"'"); + try { + URL remote = new URL(createVAppBase(_targetVersion), encodePath(_javaLocation)); + log.info("Attempting to fetch jvm at "+remote.toString()); + System.out.println("Attempting to fetch jvm at "+remote.toString()); + return new Resource(vmfile, remote, getLocalPath(vmfile), + EnumSet.of(Resource.Attr.UNPACK, Resource.Attr.CLEAN)); + } catch (Exception e) { + log.warning("Failed to create VM resource", "vmfile", vmfile, "appbase", _appbase, + "tvers", _targetVersion, "javaloc", _javaLocation, "error", e); + System.out.println("Failed to create VM resource: vmfile="+vmfile+", appbase="+_appbase+ + ", tvers="+_targetVersion+", javaloc="+_javaLocation+", error="+e); + return null; + } + } + + /** + * Returns a resource that can be used to download an archive containing all files belonging to + * the application. + */ + public Resource getFullResource () + { + String file = "full"; + try { + URL remote = new URL(createVAppBase(_targetVersion), encodePath(file)); + return new Resource(file, remote, getLocalPath(file), Resource.NORMAL); + } catch (Exception e) { + log.warning("Failed to create full resource path", + "file", file, "appbase", _appbase, "tvers", _targetVersion, "error", e); + return null; + } + } + + /** + * Returns the URL to use to report an initial download event. Returns null if no tracking + * start URL was configured for this application. + * + * @param event the event to be reported: start, jvm_start, jvm_complete, complete. + */ + public URL getTrackingURL (String event) + { + try { + String suffix = _trackingURLSuffix == null ? "" : _trackingURLSuffix; + String ga = getGATrackingCode(); + return _trackingURL == null ? null : + HostWhitelist.verify(new URL(_trackingURL + encodePath(event + suffix + ga))); + } catch (MalformedURLException mue) { + log.warning("Invalid tracking URL", "path", _trackingURL, "event", event, "error", mue); + return null; + } + } + + /** + * Returns the URL to request to report that we have reached the specified percentage of our + * initial download. Returns null if no tracking request was configured for the specified + * percentage. + */ + public URL getTrackingProgressURL (int percent) + { + if (_trackingPcts == null || !_trackingPcts.contains(percent)) { + return null; + } + return getTrackingURL("pct" + percent); + } + + /** + * Returns the name of our tracking cookie or null if it was not set. + */ + public String getTrackingCookieName () + { + return _trackingCookieName; + } + + /** + * Returns the name of our tracking cookie system property or null if it was not set. + */ + public String getTrackingCookieProperty () + { + return _trackingCookieProperty; + } + + /** + * Instructs the application to parse its {@code getdown.txt} configuration and prepare itself + * for operation. The application base URL will be parsed first so that if there are errors + * discovered later, the caller can use the application base to download a new {@code + * getdown.txt} file and try again. + * + * @return a {@code Config} instance that contains information from the config file. + * + * @exception IOException thrown if there is an error reading the file or an error encountered + * during its parsing. + */ + public Config init (boolean checkPlatform) + throws IOException + { + Config config = null; + File cfgfile = _config; + Config.ParseOpts opts = Config.createOpts(checkPlatform); + try { + // if we have a configuration file, read the data from it + if (cfgfile.exists()) { + config = Config.parseConfig(_config, opts); + } + // otherwise, try reading data from our backup config file; thanks to funny windows + // bullshit, we have to do this backup file fiddling in case we got screwed while + // updating getdown.txt during normal operation + else if ((cfgfile = getLocalPath(CONFIG_FILE + "_old")).exists()) { + config = Config.parseConfig(cfgfile, opts); + } + // otherwise, issue a warning that we found no getdown file + else { + log.info("Found no getdown.txt file", "appdir", getAppDir()); + } + } catch (Exception e) { + log.warning("Failure reading config file", "file", config, e); + } + + // if we failed to read our config file, check for an appbase specified via a system + // property; we can use that to bootstrap ourselves back into operation + if (config == null) { + String appbase = _envc.appBase; + log.info("Using 'appbase' from bootstrap config", "appbase", appbase); + Map cdata = new HashMap<>(); + cdata.put("appbase", appbase); + config = new Config(cdata); + } + + // first determine our application base, this way if anything goes wrong later in the + // process, our caller can use the appbase to download a new configuration file + _appbase = config.getString("appbase"); + if (_appbase == null) { + throw new RuntimeException("m.missing_appbase"); + } + + // check if we're overriding the domain in the appbase + _appbase = SysProps.overrideAppbase(_appbase); + + // make sure there's a trailing slash + if (!_appbase.endsWith("/")) { + _appbase = _appbase + "/"; + } + + // extract our version information + _version = config.getLong("version", -1L); + + // if we are a versioned deployment, create a versioned appbase + try { + _vappbase = createVAppBase(_version); + } catch (MalformedURLException mue) { + String err = MessageUtil.tcompose("m.invalid_appbase", _appbase); + throw (IOException) new IOException(err).initCause(mue); + } + + // check for a latest config URL + String latest = config.getString("latest"); + if (latest != null) { + if (latest.startsWith(_appbase)) { + latest = _appbase + latest.substring(_appbase.length()); + } else { + latest = SysProps.replaceDomain(latest); + } + try { + _latest = HostWhitelist.verify(new URL(latest)); + } catch (MalformedURLException mue) { + log.warning("Invalid URL for latest attribute.", mue); + } + } + + String appPrefix = _envc.appId == null ? "" : (_envc.appId + "."); + + // determine our application class name (use app-specific class _if_ one is provided) + _class = config.getString("class"); + if (appPrefix.length() > 0) { + _class = config.getString(appPrefix + "class", _class); + } + if (_class == null) { + throw new IOException("m.missing_class"); + } + + // determine whether we want strict comments + _strictComments = config.getBoolean("strict_comments"); + + // check to see if we're using a custom java.version property and regex + _javaVersionProp = config.getString("java_version_prop", _javaVersionProp); + _javaVersionRegex = config.getString("java_version_regex", _javaVersionRegex); + + // check to see if we require a particular JVM version and have a supplied JVM + _javaMinVersion = config.getLong("java_version", _javaMinVersion); + // we support java_min_version as an alias of java_version; it better expresses the check + // that's going on and better mirrors java_max_version + _javaMinVersion = config.getLong("java_min_version", _javaMinVersion); + // check to see if we require a particular max JVM version and have a supplied JVM + _javaMaxVersion = config.getLong("java_max_version", _javaMaxVersion); + // check to see if we require a particular JVM version and have a supplied JVM + _javaExactVersionRequired = config.getBoolean("java_exact_version_required"); + + // this is a little weird, but when we're run from the digester, we see a String[] which + // contains java locations for all platforms which we can't grok, but the digester doesn't + // need to know about that; when we're run in a real application there will be only one! + Object javaloc = config.getRaw("java_location"); + if (javaloc instanceof String) { + _javaLocation = (String)javaloc; + } + + // determine whether we have any tracking configuration + _trackingURL = config.getString("tracking_url"); + + // check for tracking progress percent configuration + String trackPcts = config.getString("tracking_percents"); + if (!StringUtil.isBlank(trackPcts)) { + _trackingPcts = new HashSet<>(); + for (int pct : StringUtil.parseIntArray(trackPcts)) { + _trackingPcts.add(pct); + } + } else if (!StringUtil.isBlank(_trackingURL)) { + _trackingPcts = new HashSet<>(); + _trackingPcts.add(50); + } + + // Check for tracking cookie configuration + _trackingCookieName = config.getString("tracking_cookie_name"); + _trackingCookieProperty = config.getString("tracking_cookie_property"); + + // Some app may need an extra suffix added to the tracking URL + _trackingURLSuffix = config.getString("tracking_url_suffix"); + + // Some app may need to generate google analytics code + _trackingGAHash = config.getString("tracking_ga_hash"); + + // clear our arrays as we may be reinitializing + _codes.clear(); + _resources.clear(); + _auxgroups.clear(); + _jvmargs.clear(); + _appargs.clear(); + _txtJvmArgs.clear(); + + // parse our code resources + if (config.getMultiValue("code") == null && + config.getMultiValue("ucode") == null) { + throw new IOException("m.missing_code"); + } + parseResources(config, "code", Resource.NORMAL, _codes); + parseResources(config, "ucode", Resource.UNPACK, _codes); + + // parse our non-code resources + parseResources(config, "resource", Resource.NORMAL, _resources); + parseResources(config, "uresource", Resource.UNPACK, _resources); + parseResources(config, "xresource", Resource.EXEC, _resources); + parseResources(config, "presource", Resource.PRELOAD, _resources); + parseResources(config, "nresource", Resource.NATIVE, _resources); + + // parse our auxiliary resource groups + for (String auxgroup : config.getList("auxgroups")) { + ArrayList codes = new ArrayList<>(); + parseResources(config, auxgroup + ".code", Resource.NORMAL, codes); + parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes); + ArrayList rsrcs = new ArrayList<>(); + parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs); + parseResources(config, auxgroup + ".xresource", Resource.EXEC, rsrcs); + parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs); + parseResources(config, auxgroup + ".presource", Resource.PRELOAD, rsrcs); + parseResources(config, auxgroup + ".nresource", Resource.NATIVE, rsrcs); + _auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs)); + } + + // transfer our JVM arguments (we include both "global" args and app_id-prefixed args) + String[] jvmargs = config.getMultiValue("jvmarg"); + addAll(jvmargs, _jvmargs); + if (appPrefix.length() > 0) { + jvmargs = config.getMultiValue(appPrefix + "jvmarg"); + addAll(jvmargs, _jvmargs); + } + + // see if a percentage of physical memory option exists + int jvmmempc = config.getInt("jvmmempc", -1); + // app_id prefixed setting overrides + if (appPrefix.length() > 0) { + jvmmempc = config.getInt(appPrefix + "jvmmempc", jvmmempc); + } + if (0 <= jvmmempc && jvmmempc <= 100) { + final Object o = ManagementFactory.getOperatingSystemMXBean(); + + try { + if (o instanceof OperatingSystemMXBean) { + final OperatingSystemMXBean osb = (OperatingSystemMXBean) o; + long physicalMem = osb.getTotalPhysicalMemorySize(); + long requestedMem = physicalMem*jvmmempc/100; + String[] maxMemHeapArg = new String[]{"-Xmx"+Long.toString(requestedMem)}; + // remove other max heap size arg + ARG: for (int i = 0; i < _jvmargs.size(); i++) { + if (_jvmargs.get(i) instanceof java.lang.String && _jvmargs.get(i).startsWith("-Xmx")) { + _jvmargs.remove(i); + } + } + addAll(maxMemHeapArg, _jvmargs); + + } + } + catch (NoClassDefFoundError e) { + // com.sun.management.OperatingSystemMXBean doesn't exist in this JVM + System.out.println("No com.sun.management.OperatingSystemMXBean. Cannot use 'jvmmempc'."); + } + } else if (jvmmempc != -1) { + System.out.println("'jvmmempc' value must be in range 0 to 100 (read as '"+Integer.toString(jvmmempc)+"')"); + } + + // get the set of optimum JVM arguments + _optimumJvmArgs = config.getMultiValue("optimum_jvmarg"); + + // transfer our application arguments + String[] appargs = config.getMultiValue(appPrefix + "apparg"); + addAll(appargs, _appargs); + + // add the launch specific application arguments + _appargs.addAll(_envc.appArgs); + + // look for custom arguments + fillAssignmentListFromPairs("extra.txt", _txtJvmArgs); + + // determine whether we want to allow offline operation (defaults to false) + _allowOffline = config.getBoolean("allow_offline"); + + // look for a debug.txt file which causes us to run in java.exe on Windows so that we can + // obtain a thread dump of the running JVM + _windebug = getLocalPath("debug.txt").exists(); + + // whether to cache code resources and launch from cache + _useCodeCache = config.getBoolean("use_code_cache"); + _codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7); + + // maximum simultaneous downloads + _maxConcDownloads = Math.max(1, config.getInt("max_concurrent_downloads", + SysProps.threadPoolSize())); + + // extract some info used to configure our child process on macOS + _dockName = config.getString("ui.name"); + _dockIconPath = config.getString("ui.mac_dock_icon", "../desktop.icns"); + + return config; + } + + /** + * Adds strings of the form pair0=pair1 to collector for each pair parsed out of pairLocation. + */ + protected void fillAssignmentListFromPairs (String pairLocation, List collector) + { + File pairFile = getLocalPath(pairLocation); + if (pairFile.exists()) { + try { + List args = Config.parsePairs(pairFile, Config.createOpts(false)); + for (String[] pair : args) { + if (pair[1].length() == 0) { + collector.add(pair[0]); + } else { + collector.add(pair[0] + "=" + pair[1]); + } + } + } catch (Throwable t) { + log.warning("Failed to parse '" + pairFile + "': " + t); + } + } + } + + /** + * Returns a URL from which the specified path can be fetched. Our application base URL is + * properly versioned and combined with the supplied path. + */ + public URL getRemoteURL (String path) + throws MalformedURLException + { + return new URL(_vappbase, encodePath(path)); + } + + /** + * Returns the local path to the specified resource. + */ + public File getLocalPath (String path) + { + return getLocalPath(getAppDir(), path); + } + + /** + * Returns true if we either have no version requirement, are running in a JVM that meets our + * version requirements or have what appears to be a version of the JVM that meets our + * requirements. + */ + public boolean haveValidJavaVersion () + { + // if we're doing no version checking, then yay! + if (_javaMinVersion == 0 && _javaMaxVersion == 0) return true; + + try { + // parse the version out of the java.version (or custom) system property + long version = SysProps.parseJavaVersion(_javaVersionProp, _javaVersionRegex); + + log.info("Checking Java version", "current", version, + "wantMin", _javaMinVersion, "wantMax", _javaMaxVersion); + + // if we have an unpacked VM, check the 'release' file for its version + Resource vmjar = getJavaVMResource(); + if (vmjar != null && vmjar.isMarkedValid()) { + File vmdir = new File(getAppDir(), LaunchUtil.LOCAL_JAVA_DIR); + File relfile = new File(vmdir, "release"); + if (!relfile.exists()) { + log.warning("Unpacked JVM missing 'release' file. Assuming valid version."); + return true; + } + + long vmvers = VersionUtil.readReleaseVersion(relfile, _javaVersionRegex); + if (vmvers == 0L) { + log.warning("Unable to read version from 'release' file. Assuming valid."); + return true; + } + + version = vmvers; + log.info("Checking version of unpacked JVM [vers=" + version + "]."); + } + + if (_javaExactVersionRequired) { + if (version == _javaMinVersion) return true; + else { + log.warning("An exact Java VM version is required.", "current", version, + "required", _javaMinVersion); + return false; + } + } + + boolean minVersionOK = (_javaMinVersion == 0) || (version >= _javaMinVersion); + boolean maxVersionOK = (_javaMaxVersion == 0) || (version <= _javaMaxVersion); + return minVersionOK && maxVersionOK; + + } catch (RuntimeException re) { + // if we can't parse the java version we're in weird land and should probably just try + // our luck with what we've got rather than try to download a new jvm + log.warning("Unable to parse VM version, hoping for the best", + "error", re, "needed", _javaMinVersion); + return true; + } + } + + /** + * Checks whether the app has a set of "optimum" JVM args that we wish to try first, detecting + * whether the launch is successful and, if necessary, trying again without the optimum + * arguments. + */ + public boolean hasOptimumJvmArgs () + { + return _optimumJvmArgs != null; + } + + /** + * Returns true if the app should attempt to run even if we have no Internet connection. + */ + public boolean allowOffline () + { + return _allowOffline; + } + + /** + * Attempts to redownload the getdown.txt file based on information parsed from a + * previous call to {@link #init}. + */ + public void attemptRecovery (StatusDisplay status) + throws IOException + { + status.updateStatus("m.updating_metadata"); + downloadConfigFile(); + } + + /** + * Downloads and replaces the getdown.txt and digest.txt files with + * those for the target version of our application. + */ + public void updateMetadata () + throws IOException + { + try { + // update our versioned application base with the target version + _vappbase = createVAppBase(_targetVersion); + } catch (MalformedURLException mue) { + String err = MessageUtil.tcompose("m.invalid_appbase", _appbase); + throw (IOException) new IOException(err).initCause(mue); + } + + try { + // now re-download our control files; we download the digest first so that if it fails, + // our config file will still reference the old version and re-running the updater will + // start the whole process over again + downloadDigestFiles(); + downloadConfigFile(); + + } catch (IOException ex) { + // if we are allowing offline execution, we want to allow the application to run in its + // current form rather than aborting the entire process; to do this, we delete the + // version.txt file and "trick" Getdown into thinking that it just needs to validate + // the application as is; next time the app runs when connected to the internet, it + // will have to rediscover that it needs updating and reattempt to update itself + if (_allowOffline) { + log.warning("Failed to update digest files. Attempting offline operaton.", ex); + if (!FileUtil.deleteHarder(getLocalPath(VERSION_FILE))) { + log.warning("Deleting version.txt failed. This probably isn't going to work."); + } + } else { + throw ex; + } + } + } + + /** + * Invokes the process associated with this application definition. + * + * @param optimum whether or not to include the set of optimum arguments (as opposed to falling + * back). + */ + public Process createProcess (boolean optimum) + throws IOException + { + ArrayList args = new ArrayList<>(); + + // reconstruct the path to the JVM + args.add(LaunchUtil.getJVMPath(getAppDir(), _windebug || optimum)); + + // check whether we're using -jar mode or -classpath mode + boolean dashJarMode = MANIFEST_CLASS.equals(_class); + + // add the -classpath arguments if we're not in -jar mode + ClassPath classPath = PathBuilder.buildClassPath(this); + if (!dashJarMode) { + args.add("-classpath"); + args.add(classPath.asArgumentString()); + } + + // we love our Mac users, so we do nice things to preserve our application identity + if (LaunchUtil.isMacOS()) { + args.add("-Xdock:icon=" + getLocalPath(_dockIconPath).getAbsolutePath()); + args.add("-Xdock:name=" + _dockName); + } + + // pass along our proxy settings + String proxyHost; + if ((proxyHost = System.getProperty("http.proxyHost")) != null) { + args.add("-Dhttp.proxyHost=" + proxyHost); + args.add("-Dhttp.proxyPort=" + System.getProperty("http.proxyPort")); + args.add("-Dhttps.proxyHost=" + proxyHost); + args.add("-Dhttps.proxyPort=" + System.getProperty("http.proxyPort")); + } + + // add the marker indicating the app is running in getdown + args.add("-D" + Properties.GETDOWN + "=true"); + + // set the native library path if we have native resources + // @TODO optional getdown.txt parameter to set addCurrentLibraryPath to true or false? + ClassPath javaLibPath = PathBuilder.buildLibsPath(this, true); + if (javaLibPath != null) { + args.add("-Djava.library.path=" + javaLibPath.asArgumentString()); + } + + // pass along any pass-through arguments + for (Map.Entry entry : System.getProperties().entrySet()) { + String key = (String)entry.getKey(); + if (key.startsWith(PROP_PASSTHROUGH_PREFIX)) { + key = key.substring(PROP_PASSTHROUGH_PREFIX.length()); + args.add("-D" + key + "=" + entry.getValue()); + } + } + + // add the JVM arguments + for (String string : _jvmargs) { + args.add(processArg(string)); + } + + // add the optimum arguments if requested and available + if (optimum && _optimumJvmArgs != null) { + for (String string : _optimumJvmArgs) { + args.add(processArg(string)); + } + } + + // add the arguments from extra.txt (after the optimum ones, in case they override them) + for (String string : _txtJvmArgs) { + args.add(processArg(string)); + } + + // if we're in -jar mode add those arguments, otherwise add the app class name + if (dashJarMode) { + args.add("-jar"); + args.add(classPath.asArgumentString()); + } else { + args.add(_class); + } + + // finally add the application arguments + for (String string : _appargs) { + args.add(processArg(string)); + } + + String[] envp = createEnvironment(); + String[] sargs = args.toArray(new String[args.size()]); + log.info("Running " + StringUtil.join(sargs, "\n ")); + + return Runtime.getRuntime().exec(sargs, envp, getAppDir()); + } + + /** + * If the application provided environment variables, combine those with the current + * environment and return that in a style usable for {@link Runtime#exec(String, String[])}. + * If the application didn't provide any environment variables, null is returned to just use + * the existing environment. + */ + protected String[] createEnvironment () + { + List envvar = new ArrayList<>(); + fillAssignmentListFromPairs("env.txt", envvar); + if (envvar.isEmpty()) { + log.info("Didn't find any custom environment variables, not setting any."); + return null; + } + + List envAssignments = new ArrayList<>(); + for (String assignment : envvar) { + envAssignments.add(processArg(assignment)); + } + for (Map.Entry environmentEntry : System.getenv().entrySet()) { + envAssignments.add(environmentEntry.getKey() + "=" + environmentEntry.getValue()); + } + String[] envp = envAssignments.toArray(new String[envAssignments.size()]); + log.info("Environment " + StringUtil.join(envp, "\n ")); + return envp; + } + + /** + * Runs this application directly in the current VM. + */ + public void invokeDirect () throws IOException + { + ClassPath classPath = PathBuilder.buildClassPath(this); + URL[] jarUrls = classPath.asUrls(); + + // create custom class loader + URLClassLoader loader = new URLClassLoader(jarUrls, ClassLoader.getSystemClassLoader()) { + @Override protected PermissionCollection getPermissions (CodeSource code) { + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + return perms; + } + }; + Thread.currentThread().setContextClassLoader(loader); + + log.info("Configured URL class loader:"); + for (URL url : jarUrls) log.info(" " + url); + + // configure any system properties that we can + for (String jvmarg : _jvmargs) { + if (jvmarg.startsWith("-D")) { + jvmarg = processArg(jvmarg.substring(2)); + int eqidx = jvmarg.indexOf("="); + if (eqidx == -1) { + log.warning("Bogus system property: '" + jvmarg + "'?"); + } else { + System.setProperty(jvmarg.substring(0, eqidx), jvmarg.substring(eqidx+1)); + } + } + } + + // pass along any pass-through arguments + Map passProps = new HashMap<>(); + for (Map.Entry entry : System.getProperties().entrySet()) { + String key = (String)entry.getKey(); + if (key.startsWith(PROP_PASSTHROUGH_PREFIX)) { + key = key.substring(PROP_PASSTHROUGH_PREFIX.length()); + passProps.put(key, (String)entry.getValue()); + } + } + // we can't set these in the above loop lest we get a ConcurrentModificationException + for (Map.Entry entry : passProps.entrySet()) { + System.setProperty(entry.getKey(), entry.getValue()); + } + + // prepare our app arguments + String[] args = new String[_appargs.size()]; + for (int ii = 0; ii < args.length; ii++) args[ii] = processArg(_appargs.get(ii)); + + try { + log.info("Loading " + _class); + Class appclass = loader.loadClass(_class); + Method main = appclass.getMethod("main", EMPTY_STRING_ARRAY.getClass()); + log.info("Invoking main({" + StringUtil.join(args, ", ") + "})"); + main.invoke(null, new Object[] { args }); + } catch (Exception e) { + log.warning("Failure invoking app main", e); + } + } + + /** Replaces the application directory and version in any argument. */ + protected String processArg (String arg) + { + arg = arg.replace("%APPDIR%", getAppDir().getAbsolutePath()); + arg = arg.replace("%VERSION%", String.valueOf(_version)); + + // if this argument contains %ENV.FOO% replace those with the associated values looked up + // from the environment + if (arg.contains(ENV_VAR_PREFIX)) { + StringBuffer sb = new StringBuffer(); + Matcher matcher = ENV_VAR_PATTERN.matcher(arg); + while (matcher.find()) { + String varName = matcher.group(1), varValue = System.getenv(varName); + String repValue = varValue == null ? "MISSING:"+varName : varValue; + matcher.appendReplacement(sb, Matcher.quoteReplacement(repValue)); + } + matcher.appendTail(sb); + arg = sb.toString(); + } + + return arg; + } + + /** + * Loads the digest.txt file and verifies the contents of both that file and the + * getdown.text file. Then it loads the version.txt and decides + * whether or not the application needs to be updated or whether we can proceed to verification + * and execution. + * + * @return true if the application needs to be updated, false if it is up to date and can be + * verified and executed. + * + * @exception IOException thrown if we encounter an unrecoverable error while verifying the + * metadata. + */ + public boolean verifyMetadata (StatusDisplay status) + throws IOException + { + log.info("Verifying application: " + _vappbase); + log.info("Version: " + _version); + log.info("Class: " + _class); + + // this will read in the contents of the digest file and validate itself + try { + _digest = new Digest(getAppDir(), _strictComments); + } catch (IOException ioe) { + log.info("Failed to load digest: " + ioe.getMessage() + ". Attempting recovery..."); + } + + // if we have no version, then we are running in unversioned mode so we need to download + // our digest.txt file on every invocation + if (_version == -1) { + // make a note of the old meta-digest, if this changes we need to revalidate all of our + // resources as one or more of them have also changed + String olddig = (_digest == null) ? "" : _digest.getMetaDigest(); + try { + status.updateStatus("m.checking"); + downloadDigestFiles(); + _digest = new Digest(getAppDir(), _strictComments); + if (!olddig.equals(_digest.getMetaDigest())) { + log.info("Unversioned digest changed. Revalidating..."); + status.updateStatus("m.validating"); + clearValidationMarkers(); + } + } catch (IOException ioe) { + log.warning("Failed to refresh non-versioned digest: " + + ioe.getMessage() + ". Proceeding..."); + } + } + + // regardless of whether we're versioned, if we failed to read the digest from disk, try to + // redownload the digest file and give it another good college try; this time we allow + // exceptions to propagate up to the caller as there is nothing else we can do + if (_digest == null) { + status.updateStatus("m.updating_metadata"); + downloadDigestFiles(); + _digest = new Digest(getAppDir(), _strictComments); + } + + // now verify the contents of our main config file + Resource crsrc = getConfigResource(); + if (!_digest.validateResource(crsrc, null)) { + status.updateStatus("m.updating_metadata"); + // attempt to redownload both of our metadata files; again we pass errors up to our + // caller because there's nothing we can do to automatically recover + downloadConfigFile(); + downloadDigestFiles(); + _digest = new Digest(getAppDir(), _strictComments); + // revalidate everything if we end up downloading new metadata + clearValidationMarkers(); + // if the new copy validates, reinitialize ourselves; otherwise report baffling hoseage + if (_digest.validateResource(crsrc, null)) { + init(true); + } else { + log.warning(CONFIG_FILE + " failed to validate even after redownloading. " + + "Blindly forging onward."); + } + } + + // start by assuming we are happy with our version + _targetVersion = _version; + + // if we are a versioned application, read in the contents of the version.txt file + // and/or check the latest config URL for a newer version + if (_version != -1) { + File vfile = getLocalPath(VERSION_FILE); + long fileVersion = VersionUtil.readVersion(vfile); + if (fileVersion != -1) { + _targetVersion = fileVersion; + } + + if (_latest != null) { + try (InputStream in = ConnectionUtil.open(proxy, _latest, 0, 0).getInputStream(); + InputStreamReader reader = new InputStreamReader(in, UTF_8); + BufferedReader bin = new BufferedReader(reader)) { + for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) { + if (pair[0].equals("version")) { + _targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion); + if (fileVersion != -1 && _targetVersion > fileVersion) { + // replace the file with the newest version + try (FileOutputStream fos = new FileOutputStream(vfile); + PrintStream out = new PrintStream(fos)) { + out.println(_targetVersion); + } + } + break; + } + } + } catch (Exception e) { + log.warning("Unable to retrieve version from latest config file.", e); + } + } + } + + // finally let the caller know if we need an update + return _version != _targetVersion; + } + + /** + * Verifies the code and media resources associated with this application. A list of resources + * that do not exist or fail the verification process will be returned. If all resources are + * ready to go, null will be returned and the application is considered ready to run. + * + * @param obs a progress observer that will be notified of verification progress. NOTE: this + * observer may be called from arbitrary threads, so if you update a UI based on calls to it, + * you have to take care to get back to your UI thread. + * @param alreadyValid if non-null a 1 element array that will have the number of "already + * validated" resources filled in. + * @param unpacked a set to populate with unpacked resources. + * @param toInstall a list into which to add resources that need to be installed. + * @param toDownload a list into which to add resources that need to be downloaded. + */ + public void verifyResources ( + ProgressObserver obs, int[] alreadyValid, Set unpacked, + Set toInstall, Set toDownload) + throws InterruptedException + { + // resources are verified on background threads supplied by the thread pool, and progress + // is reported by posting runnable actions to the actions queue which is processed by the + // main (UI) thread + ExecutorService exec = Executors.newFixedThreadPool(SysProps.threadPoolSize()); + final BlockingQueue actions = new LinkedBlockingQueue(); + final int[] completed = new int[1]; + + long start = System.currentTimeMillis(); + + // obtain the sizes of the resources to validate + List rsrcs = getAllActiveResources(); + long[] sizes = new long[rsrcs.size()]; + long totalSize = 0; + for (int ii = 0; ii < sizes.length; ii++) { + totalSize += sizes[ii] = rsrcs.get(ii).getLocal().length(); + } + final ProgressObserver fobs = obs; + // as long as we forward aggregated progress updates to the UI thread, having multiple + // threads update a progress aggregator is "mostly" thread-safe + final ProgressAggregator pagg = new ProgressAggregator(new ProgressObserver() { + public void progress (final int percent) { + actions.add(new Runnable() { + public void run () { + fobs.progress(percent); + } + }); + } + }, sizes); + + final int[] fAlreadyValid = alreadyValid; + final Set toInstallAsync = new ConcurrentSkipListSet<>(toInstall); + final Set toDownloadAsync = new ConcurrentSkipListSet<>(); + final Set unpackedAsync = new ConcurrentSkipListSet<>(); + + for (int ii = 0; ii < sizes.length; ii++) { + final Resource rsrc = rsrcs.get(ii); + final int index = ii; + exec.execute(new Runnable() { + public void run () { + verifyResource(rsrc, pagg.startElement(index), fAlreadyValid, + unpackedAsync, toInstallAsync, toDownloadAsync); + actions.add(new Runnable() { + public void run () { + completed[0] += 1; + } + }); + } + }); + } + + while (completed[0] < rsrcs.size()) { + // we should be getting progress completion updates WAY more often than one every + // minute, so if things freeze up for 60 seconds, abandon ship + Runnable action = actions.poll(60, TimeUnit.SECONDS); + action.run(); + } + + exec.shutdown(); + + toInstall.addAll(toInstallAsync); + toDownload.addAll(toDownloadAsync); + unpacked.addAll(unpackedAsync); + + long complete = System.currentTimeMillis(); + log.info("Verified resources", "count", rsrcs.size(), "size", (totalSize/1024) + "k", + "duration", (complete-start) + "ms"); + } + + private void verifyResource (Resource rsrc, ProgressObserver obs, int[] alreadyValid, + Set unpacked, + Set toInstall, Set toDownload) { + if (rsrc.isMarkedValid()) { + if (alreadyValid != null) { + alreadyValid[0]++; + } + obs.progress(100); + return; + } + + try { + if (_digest.validateResource(rsrc, obs)) { + // if the resource has a _new file, add it to to-install list + if (rsrc.getLocalNew().exists()) { + toInstall.add(rsrc); + return; + } + rsrc.applyAttrs(); + unpacked.add(rsrc); + rsrc.markAsValid(); + return; + } + + } catch (Exception e) { + log.info("Failure verifying resource. Requesting redownload...", + "rsrc", rsrc, "error", e); + + } finally { + obs.progress(100); + } + toDownload.add(rsrc); + } + + /** + * Unpacks the resources that require it (we know that they're valid). + * + * @param unpacked a set of resources to skip because they're already unpacked. + */ + public void unpackResources (ProgressObserver obs, Set unpacked) + throws InterruptedException + { + List rsrcs = getActiveResources(); + + // remove resources that we don't want to unpack + for (Iterator it = rsrcs.iterator(); it.hasNext(); ) { + Resource rsrc = it.next(); + if (!rsrc.shouldUnpack() || unpacked.contains(rsrc)) { + it.remove(); + } + } + + // obtain the sizes of the resources to unpack + long[] sizes = new long[rsrcs.size()]; + for (int ii = 0; ii < sizes.length; ii++) { + sizes[ii] = rsrcs.get(ii).getLocal().length(); + } + + ProgressAggregator pagg = new ProgressAggregator(obs, sizes); + for (int ii = 0; ii < sizes.length; ii++) { + Resource rsrc = rsrcs.get(ii); + ProgressObserver pobs = pagg.startElement(ii); + try { + rsrc.unpack(); + } catch (IOException ioe) { + log.warning("Failure unpacking resource", "rsrc", rsrc, ioe); + } + pobs.progress(100); + } + } + + /** + * Clears all validation marker files. + */ + public void clearValidationMarkers () + { + clearValidationMarkers(getAllActiveResources().iterator()); + } + + /** + * Returns the version number for the application. Should only be called after successful + * return of verifyMetadata. + */ + public long getVersion () + { + return _version; + } + + /** + * Creates a versioned application base URL for the specified version. + */ + protected URL createVAppBase (long version) + throws MalformedURLException + { + String url = version < 0 ? _appbase : _appbase.replace("%VERSION%", "" + version); + return HostWhitelist.verify(new URL(url)); + } + + /** + * Clears all validation marker files for the resources in the supplied iterator. + */ + protected void clearValidationMarkers (Iterator iter) + { + while (iter.hasNext()) { + iter.next().clearMarker(); + } + } + + /** + * Downloads a new copy of CONFIG_FILE. + */ + protected void downloadConfigFile () + throws IOException + { + downloadControlFile(CONFIG_FILE, 0); + } + + /** + * @return true if gettingdown.lock was unlocked, already locked by this application or if + * we're not locking at all. + */ + public synchronized boolean lockForUpdates () + { + if (_lock != null && _lock.isValid()) { + return true; + } + try { + _lockChannel = new RandomAccessFile(getLocalPath("gettingdown.lock"), "rw").getChannel(); + } catch (FileNotFoundException e) { + log.warning("Unable to create lock file", "message", e.getMessage(), e); + return false; + } + try { + _lock = _lockChannel.tryLock(); + } catch (IOException e) { + log.warning("Unable to create lock", "message", e.getMessage(), e); + return false; + } catch (OverlappingFileLockException e) { + log.warning("The lock is held elsewhere in this JVM", e); + return false; + } + log.info("Able to lock for updates: " + (_lock != null)); + return _lock != null; + } + + /** + * Release gettingdown.lock + */ + public synchronized void releaseLock () + { + if (_lock != null) { + log.info("Releasing lock"); + try { + _lock.release(); + } catch (IOException e) { + log.warning("Unable to release lock", "message", e.getMessage(), e); + } + try { + _lockChannel.close(); + } catch (IOException e) { + log.warning("Unable to close lock channel", "message", e.getMessage(), e); + } + _lockChannel = null; + _lock = null; + } + } + + /** + * Downloads the digest files and validates their signature. + * @throws IOException + */ + protected void downloadDigestFiles () + throws IOException + { + for (int version = 1; version <= Digest.VERSION; version++) { + downloadControlFile(Digest.digestFile(version), version); + } + } + + /** + * Downloads a new copy of the specified control file, optionally validating its signature. + * If the download is successful, moves it over the old file on the filesystem. + * + *

TODO: Switch to PKCS #7 or CMS. + * + * @param sigVersion if {@code 0} no validation will be performed, if {@code > 0} then this + * should indicate the version of the digest file being validated which indicates which + * algorithm to use to verify the signature. See {@link Digest#VERSION}. + */ + protected void downloadControlFile (String path, int sigVersion) + throws IOException + { + File target = downloadFile(path); + + if (sigVersion > 0) { + if (_envc.certs.isEmpty()) { + log.info("No signing certs, not verifying digest.txt", "path", path); + + } else { + File signatureFile = downloadFile(path + SIGNATURE_SUFFIX); + byte[] signature = null; + try (FileInputStream signatureStream = new FileInputStream(signatureFile)) { + signature = StreamUtil.toByteArray(signatureStream); + } finally { + FileUtil.deleteHarder(signatureFile); // delete the file regardless + } + + byte[] buffer = new byte[8192]; + int length, validated = 0; + for (Certificate cert : _envc.certs) { + try (FileInputStream dataInput = new FileInputStream(target)) { + Signature sig = Signature.getInstance(Digest.sigAlgorithm(sigVersion)); + sig.initVerify(cert); + while ((length = dataInput.read(buffer)) != -1) { + sig.update(buffer, 0, length); + } + + if (!sig.verify(Base64.decode(signature, Base64.DEFAULT))) { + log.info("Signature does not match", "cert", cert.getPublicKey()); + continue; + } else { + log.info("Signature matches", "cert", cert.getPublicKey()); + validated++; + } + + } catch (IOException ioe) { + log.warning("Failure validating signature of " + target + ": " + ioe); + + } catch (GeneralSecurityException gse) { + // no problem! + + } + } + + // if we couldn't find a key that validates our digest, we are the hosed! + if (validated == 0) { + // delete the temporary digest file as we know it is invalid + FileUtil.deleteHarder(target); + throw new IOException("m.corrupt_digest_signature_error"); + } + } + } + + // now move the temporary file over the original + File original = getLocalPath(path); + if (!FileUtil.renameTo(target, original)) { + throw new IOException("Failed to rename(" + target + ", " + original + ")"); + } + } + + /** + * Download a path to a temporary file, returning a {@link File} instance with the path + * contents. + */ + protected File downloadFile (String path) + throws IOException + { + File target = getLocalPath(path + "_new"); + + URL targetURL = null; + try { + targetURL = getRemoteURL(path); + } catch (Exception e) { + log.warning("Requested to download invalid control file", + "appbase", _vappbase, "path", path, "error", e); + throw (IOException) new IOException("Invalid path '" + path + "'.").initCause(e); + } + + log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'."); + + // stream the URL into our temporary file + URLConnection uconn = ConnectionUtil.open(proxy, targetURL, 0, 0); + // we have to tell Java not to use caches here, otherwise it will cache any request for + // same URL for the lifetime of this JVM (based on the URL string, not the URL object); + // if the getdown.txt file, for example, changes in the meanwhile, we would never hear + // about it; turning off caches is not a performance concern, because when Getdown asks + // to download a file, it expects it to come over the wire, not from a cache + uconn.setUseCaches(false); + uconn.setRequestProperty("Accept-Encoding", "gzip"); + try (InputStream fin = uconn.getInputStream()) { + String encoding = uconn.getContentEncoding(); + boolean gzip = "gzip".equalsIgnoreCase(encoding); + try (InputStream fin2 = (gzip ? new GZIPInputStream(fin) : fin)) { + try (FileOutputStream fout = new FileOutputStream(target)) { + StreamUtil.copy(fin2, fout); + } + } + } + + return target; + } + + /** Helper function for creating {@link Resource} instances. */ + protected Resource createResource (String path, EnumSet attrs) + throws MalformedURLException + { + return new Resource(path, getRemoteURL(path), getLocalPath(path), attrs); + } + + /** Helper function to add all values in {@code values} (if non-null) to {@code target}. */ + protected static void addAll (String[] values, List target) { + if (values != null) { + for (String value : values) { + target.add(value); + } + } + } + + /** + * Make an immutable List from the specified int array. + */ + public static List intsToList (int[] values) + { + List list = new ArrayList<>(values.length); + for (int val : values) { + list.add(val); + } + return Collections.unmodifiableList(list); + } + + /** + * Make an immutable List from the specified String array. + */ + public static List stringsToList (String[] values) + { + return values == null ? null : Collections.unmodifiableList(Arrays.asList(values)); + } + + /** Used to parse resources with the specified name. */ + protected void parseResources (Config config, String name, EnumSet attrs, + List list) + { + String[] rsrcs = config.getMultiValue(name); + if (rsrcs == null) { + return; + } + for (String rsrc : rsrcs) { + try { + list.add(createResource(rsrc, attrs)); + } catch (Exception e) { + log.warning("Invalid resource '" + rsrc + "'. " + e); + } + } + } + + /** Possibly generates and returns a google analytics tracking cookie. */ + protected String getGATrackingCode () + { + if (_trackingGAHash == null) { + return ""; + } + long time = System.currentTimeMillis() / 1000; + if (_trackingStart == 0) { + _trackingStart = time; + } + if (_trackingId == 0) { + int low = 100000000, high = 1000000000; + _trackingId = low + _rando.nextInt(high-low); + } + StringBuilder cookie = new StringBuilder("&utmcc=__utma%3D").append(_trackingGAHash); + cookie.append(".").append(_trackingId); + cookie.append(".").append(_trackingStart).append(".").append(_trackingStart); + cookie.append(".").append(time).append(".1%3B%2B"); + cookie.append("__utmz%3D").append(_trackingGAHash).append("."); + cookie.append(_trackingStart).append(".1.1."); + cookie.append("utmcsr%3D(direct)%7Cutmccn%3D(direct)%7Cutmcmd%3D(none)%3B"); + int low = 1000000000, high = 2000000000; + cookie.append("&utmn=").append(_rando.nextInt(high-low)); + return cookie.toString(); + } + + /** + * Encodes a path for use in a URL. + */ + protected static String encodePath (String path) + { + try { + // we want to keep slashes because we're encoding an entire path; also we need to turn + // + into %20 because web servers don't like + in paths or file names, blah + return URLEncoder.encode(path, "UTF-8").replace("%2F", "/").replace("+", "%20"); + } catch (UnsupportedEncodingException ue) { + log.warning("Failed to URL encode " + path + ": " + ue); + return path; + } + } + + protected File getLocalPath (File appdir, String path) + { + return new File(appdir, path); + } + + protected final EnvConfig _envc; + protected File _config; + protected Digest _digest; + + protected long _version = -1; + protected long _targetVersion = -1; + protected String _appbase; + protected URL _vappbase; + protected URL _latest; + protected String _class; + protected String _dockName; + protected String _dockIconPath; + protected boolean _strictComments; + protected boolean _windebug; + protected boolean _allowOffline; + protected int _maxConcDownloads; + + protected String _trackingURL; + protected Set _trackingPcts; + protected String _trackingCookieName; + protected String _trackingCookieProperty; + protected String _trackingURLSuffix; + protected String _trackingGAHash; + protected long _trackingStart; + protected int _trackingId; + + protected String _javaVersionProp = "java.version"; + protected String _javaVersionRegex = "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?"; + protected long _javaMinVersion, _javaMaxVersion; + protected boolean _javaExactVersionRequired; + protected String _javaLocation; + + protected List _codes = new ArrayList<>(); + protected List _resources = new ArrayList<>(); + + protected boolean _useCodeCache; + protected int _codeCacheRetentionDays; + + protected Map _auxgroups = new HashMap<>(); + protected Map _auxactive = new HashMap<>(); + + protected List _jvmargs = new ArrayList<>(); + protected List _appargs = new ArrayList<>(); + + protected String[] _optimumJvmArgs; + + protected List _txtJvmArgs = new ArrayList<>(); + + /** If a warning has been issued about not being able to set modtimes. */ + protected boolean _warnedAboutSetLastModified; + + /** Locks gettingdown.lock in the app dir. Held the entire time updating is going on.*/ + protected FileLock _lock; + + /** Channel to the file underlying _lock. Kept around solely so the lock doesn't close. */ + protected FileChannel _lockChannel; + + protected Random _rando = new Random(); + + protected static final String[] EMPTY_STRING_ARRAY = new String[0]; + + protected static final String ENV_VAR_PREFIX = "%ENV."; + protected static final Pattern ENV_VAR_PATTERN = Pattern.compile("%ENV\\.(.*?)%"); +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java new file mode 100644 index 0000000..19a9fc5 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java @@ -0,0 +1,43 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2016 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.util.Arrays; +import java.util.List; + +import com.threerings.getdown.util.StringUtil; + +/** + * Contains static data provided during the build process. + */ +public class Build { + + /** The date and time at which the code was built: in {@code yyyy-MM-dd HH:mm} format. */ + public static String time () { + return "2019-04-05 14:07"; + } + + /** The Maven version of the Getdown project. */ + public static String version () { + return "1.8.3-SNAPSHOT"; + } + + /** + *

The hosts which Getdown is allowed to communicate with. An empty list indicates that + * no whitelist is configured and there are no limitations. By default, no host whitelist + * is added to the binary, so it can be used to download and run applications from any + * server. + * + *

To create a custom Getdown build that can only talk to whitelisted servers, set + * the {@code getdown.host.whitelist} property on the command line while building the JAR + * (e.g. {@code mvn package -Dgetdown.host.whitelist=my.server.com}). Wildcards can be used + * (e.g. {@code *.mycompany.com}) and multiple values can be separated by commas + * (e.g. {@code app1.foo.com,app2.bar.com,app3.baz.com}). + */ + public static List hostWhitelist () { + return Arrays.asList(StringUtil.parseStringArray("")); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl new file mode 100644 index 0000000..60a8ff3 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl @@ -0,0 +1,43 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2016 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.util.Arrays; +import java.util.List; + +import com.threerings.getdown.util.StringUtil; + +/** + * Contains static data provided during the build process. + */ +public class Build { + + /** The date and time at which the code was built: in {@code yyyy-MM-dd HH:mm} format. */ + public static String time () { + return "@build_time@"; + } + + /** The Maven version of the Getdown project. */ + public static String version () { + return "@build_version@"; + } + + /** + *

The hosts which Getdown is allowed to communicate with. An empty list indicates that + * no whitelist is configured and there are no limitations. By default, no host whitelist + * is added to the binary, so it can be used to download and run applications from any + * server. + * + *

To create a custom Getdown build that can only talk to whitelisted servers, set + * the {@code getdown.host.whitelist} property on the command line while building the JAR + * (e.g. {@code mvn package -Dgetdown.host.whitelist=my.server.com}). Wildcards can be used + * (e.g. {@code *.mycompany.com}) and multiple values can be separated by commas + * (e.g. {@code app1.foo.com,app2.bar.com,app3.baz.com}). + */ + public static List hostWhitelist () { + return Arrays.asList(StringUtil.parseStringArray("@host_whitelist@")); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java new file mode 100644 index 0000000..9c2fce3 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java @@ -0,0 +1,76 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Represents the class path and it's elements of the application to be launched. The class path + * can either be represented as an {@link #asArgumentString() argument string} for the java command + * line or as an {@link #asUrls() array of URLs} to be used by a {@link URLClassLoader}. + */ +public class ClassPath +{ + public ClassPath (LinkedHashSet classPathEntries) + { + _classPathEntries = Collections.unmodifiableSet(classPathEntries); + } + + /** + * Returns the class path as an java command line argument string, e.g. + * + *

+     *   /path/to/a.jar:/path/to/b.jar
+     * 
+ */ + public String asArgumentString () + { + StringBuilder builder = new StringBuilder(); + String delimiter = ""; + for (File entry: _classPathEntries) { + builder.append(delimiter).append(entry.getAbsolutePath()); + delimiter = File.pathSeparator; + } + return builder.toString(); + } + + /** + * Returns the class path entries as an array of URLs to be used for example by an + * {@link URLClassLoader}. + */ + public URL[] asUrls () + { + URL[] urls = new URL[_classPathEntries.size()]; + int i = 0; + for (File entry : _classPathEntries) { + urls[i++] = getURL(entry); + } + return urls; + } + + public Set getClassPathEntries () + { + return _classPathEntries; + } + + + private static URL getURL (File file) + { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new IllegalStateException("URL of file is illegal: " + file.getAbsolutePath(), e); + } + } + + private final Set _classPathEntries; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java new file mode 100644 index 0000000..bc8d140 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java @@ -0,0 +1,228 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.concurrent.*; + +import com.threerings.getdown.util.Config; +import com.threerings.getdown.util.MessageUtil; +import com.threerings.getdown.util.ProgressObserver; +import com.threerings.getdown.util.StringUtil; + +import static com.threerings.getdown.Log.log; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Manages the digest.txt file and the computing and processing of digests for an + * application. + */ +public class Digest +{ + /** The current version of the digest protocol. */ + public static final int VERSION = 2; + + /** + * Returns the name of the digest file for the specified protocol version. + */ + public static String digestFile (int version) { + String infix = version > 1 ? String.valueOf(version) : ""; + return FILE_NAME + infix + FILE_SUFFIX; + } + + /** + * Returns the crypto algorithm used to sign digest files of the specified version. + */ + public static String sigAlgorithm (int version) { + switch (version) { + case 1: return "SHA1withRSA"; + case 2: return "SHA256withRSA"; + default: throw new IllegalArgumentException("Invalid digest version " + version); + } + } + + /** + * Creates a digest file at the specified location using the supplied list of resources. + * @param version the version of the digest protocol to use. + */ + public static void createDigest (int version, List resources, File output) + throws IOException + { + // first compute the digests for all the resources in parallel + ExecutorService exec = Executors.newFixedThreadPool(SysProps.threadPoolSize()); + final Map digests = new ConcurrentHashMap<>(); + final BlockingQueue completed = new LinkedBlockingQueue<>(); + final int fversion = version; + + long start = System.currentTimeMillis(); + + Set pending = new HashSet<>(resources); + for (final Resource rsrc : resources) { + exec.execute(new Runnable() { + public void run () { + try { + MessageDigest md = getMessageDigest(fversion); + digests.put(rsrc, rsrc.computeDigest(fversion, md, null)); + completed.add(rsrc); + } catch (Throwable t) { + completed.add(new IOException("Error computing digest for: " + rsrc). + initCause(t)); + } + } + }); + } + + // queue a shutdown of the thread pool when the tasks are done + exec.shutdown(); + + try { + while (pending.size() > 0) { + Object done = completed.poll(600, TimeUnit.SECONDS); + if (done instanceof IOException) { + throw (IOException)done; + } else if (done instanceof Resource) { + pending.remove((Resource)done); + } else { + throw new AssertionError("What is this? " + done); + } + } + } catch (InterruptedException ie) { + throw new IOException("Timeout computing digests. Wow."); + } + + StringBuilder data = new StringBuilder(); + try (FileOutputStream fos = new FileOutputStream(output); + OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); + PrintWriter pout = new PrintWriter(osw)) { + // compute and append the digest of each resource in the list + for (Resource rsrc : resources) { + String path = rsrc.getPath(); + String digest = digests.get(rsrc); + note(data, path, digest); + pout.println(path + " = " + digest); + } + // finally compute and append the digest for the file contents + MessageDigest md = getMessageDigest(version); + byte[] contents = data.toString().getBytes(UTF_8); + String filename = digestFile(version); + pout.println(filename + " = " + StringUtil.hexlate(md.digest(contents))); + } + + long elapsed = System.currentTimeMillis() - start; + log.debug("Computed digests [rsrcs=" + resources.size() + ", time=" + elapsed + "ms]"); + } + + /** + * Obtains an appropriate message digest instance for use by the Getdown system. + */ + public static MessageDigest getMessageDigest (int version) + { + String algo = version > 1 ? "SHA-256" : "MD5"; + try { + return MessageDigest.getInstance(algo); + } catch (NoSuchAlgorithmException nsae) { + throw new RuntimeException("JVM does not support " + algo + ". Gurp!"); + } + } + + /** + * Creates a digest instance which will parse and validate the digest in the supplied + * application directory, using the current digest version. + */ + public Digest (File appdir, boolean strictComments) throws IOException { + this(appdir, VERSION, strictComments); + } + + /** + * Creates a digest instance which will parse and validate the digest in the supplied + * application directory. + * @param version the version of the digest protocol to use. + */ + public Digest (File appdir, int version, boolean strictComments) throws IOException + { + // parse and validate our digest file contents + String filename = digestFile(version); + StringBuilder data = new StringBuilder(); + File dfile = new File(appdir, filename); + Config.ParseOpts opts = Config.createOpts(false); + opts.strictComments = strictComments; + // bias = toward key: the key is the filename and could conceivably contain = signs, value + // is the hex encoded hash which will not contain = + opts.biasToKey = true; + for (String[] pair : Config.parsePairs(dfile, opts)) { + if (pair[0].equals(filename)) { + _metaDigest = pair[1]; + break; + } + _digests.put(pair[0], pair[1]); + note(data, pair[0], pair[1]); + } + + // we've reached the end, validate our contents + MessageDigest md = getMessageDigest(version); + byte[] contents = data.toString().getBytes(UTF_8); + String hash = StringUtil.hexlate(md.digest(contents)); + if (!hash.equals(_metaDigest)) { + String err = MessageUtil.tcompose("m.invalid_digest_file", _metaDigest, hash); + throw new IOException(err); + } + } + + /** + * Returns the digest for the digest file. + */ + public String getMetaDigest () + { + return _metaDigest; + } + + /** + * Computes the hash of the specified resource and compares it with the value parsed from + * the digest file. Logs a message if the resource fails validation. + * + * @return true if the resource is valid, false if it failed the digest check or if an I/O + * error was encountered during the validation process. + */ + public boolean validateResource (Resource resource, ProgressObserver obs) + { + try { + String chash = resource.computeDigest(VERSION, getMessageDigest(VERSION), obs); + String ehash = _digests.get(resource.getPath()); + if (chash.equals(ehash)) { + return true; + } + log.info("Resource failed digest check", + "rsrc", resource, "computed", chash, "expected", ehash); + } catch (Throwable t) { + log.info("Resource failed digest check", "rsrc", resource, "error", t); + } + return false; + } + + /** + * Returns the digest of the given {@code resource}. + */ + public String getDigest (Resource resource) + { + return _digests.get(resource.getPath()); + } + + /** Used by {@link #createDigest} and {@link Digest}. */ + protected static void note (StringBuilder data, String path, String digest) + { + data.append(path).append(" = ").append(digest).append("\n"); + } + + protected HashMap _digests = new HashMap<>(); + protected String _metaDigest = ""; + + protected static final String FILE_NAME = "digest"; + protected static final String FILE_SUFFIX = ".txt"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java new file mode 100644 index 0000000..6de4e2e --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java @@ -0,0 +1,229 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.io.FileInputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.*; + +import com.threerings.getdown.util.StringUtil; + +/** Configuration that comes from our "environment" (command line args, sys props, etc.). */ +public final class EnvConfig { + + /** Used to report problems or feedback by {@link #create}. */ + public static final class Note { + public static enum Level { INFO, WARN, ERROR }; + public static Note info (String msg) { return new Note(Level.INFO, msg); } + public static Note warn (String msg) { return new Note(Level.WARN, msg); } + public static Note error (String msg) { return new Note(Level.ERROR, msg); } + public final Level level; + public final String message; + public Note (Level level, String message) { + this.level = level; + this.message = message; + } + } + + /** + * Creates an environment config, obtaining information (in order) from the following sources: + * + *
    + *
  • A {@code bootstrap.properties} file bundled with the jar.
  • + *
  • System properties supplied to the JVM.
  • + *
  • The supplied command line arguments ({@code argv}).
  • + *
+ * + * If a later source supplies a configuration already provided by a prior source, a warning + * message will be logged to indicate the conflict, and the prior source will be used. + * + * @param notes a list into which notes are added, to be logged after the logging system has + * been initialized (which cannot happen until the appdir is known). If any {@code ERROR} notes + * are included, the app should terminate after reporting them. + * @return an env config instance, or {@code null} if no appdir could be located via any + * configuration source. + */ + public static EnvConfig create (String[] argv, List notes) { + String appDir = null, appDirProv = null; + String appId = null, appIdProv = null; + String appBase = null, appBaseProv = null; + + // start with bootstrap.properties config, if avaialble + try { + ResourceBundle bundle = ResourceBundle.getBundle("bootstrap"); + if (bundle.containsKey("appdir")) { + appDir = bundle.getString("appdir"); + appDir = appDir.replace(USER_HOME_KEY, System.getProperty("user.home")); + appDirProv = "bootstrap.properties"; + } + if (bundle.containsKey("appid")) { + appId = bundle.getString("appid"); + appIdProv = "bootstrap.properties"; + } + if (bundle.containsKey("appbase")) { + appBase = bundle.getString("appbase"); + appBaseProv = "bootstrap.properties"; + } + // if any system properties are specified (keys prefixed with sys.), set those up + for (String key : bundle.keySet()) { + if (key.startsWith("sys.")) { + String skey = key.substring(4); + String svalue = bundle.getString(key); + notes.add(Note.info("Setting system property from bundle: " + + skey + "='" + svalue + "'")); + System.setProperty(skey, svalue); + } + } + + } catch (MissingResourceException e) { + // bootstrap.properties is optional; no need for a warning + } + + // next seek config from system properties + String spropsAppDir = SysProps.appDir(); + if (!StringUtil.isBlank(spropsAppDir)) { + if (appDir == null) { + appDir = spropsAppDir; + appDirProv = "system property"; + } else { + notes.add(Note.warn("Ignoring 'appdir' system property, have appdir via '" + + appDirProv + "'")); + } + } + String spropsAppId = SysProps.appId(); + if (!StringUtil.isBlank(spropsAppId)) { + if (appId == null) { + appId = spropsAppId; + appIdProv = "system property"; + } else { + notes.add(Note.warn("Ignoring 'appid' system property, have appid via '" + + appIdProv + "'")); + } + } + String spropsAppBase = SysProps.appBase(); + if (!StringUtil.isBlank(spropsAppBase)) { + if (appBase == null) { + appBase = spropsAppBase; + appBaseProv = "system property"; + } else { + notes.add(Note.warn("Ignoring 'appbase' system property, have appbase via '" + + appBaseProv + "'")); + } + } + + // finally obtain config from command line arguments + String argvAppDir = argv.length > 0 ? argv[0] : null; + if (!StringUtil.isBlank(argvAppDir)) { + if (appDir == null) { + appDir = argvAppDir; + appDirProv = "command line"; + } else { + notes.add(Note.warn("Ignoring 'appdir' command line arg, have appdir via '" + + appDirProv + "'")); + } + } + String argvAppId = argv.length > 1 ? argv[1] : null; + if (!StringUtil.isBlank(argvAppId)) { + if (appId == null) { + appId = argvAppId; + appIdProv = "command line"; + } else { + notes.add(Note.warn("Ignoring 'appid' command line arg, have appid via '" + + appIdProv + "'")); + } + } + + // ensure that we were able to fine an app dir + if (appDir == null) { + return null; // caller will report problem to user + } + + notes.add(Note.info("Using appdir from " + appDirProv + ": " + appDir)); + if (appId != null) notes.add(Note.info("Using appid from " + appIdProv + ": " + appId)); + if (appBase != null) notes.add( + Note.info("Using appbase from " + appBaseProv + ": " + appBase)); + + // ensure that the appdir refers to a directory that exists + File appDirFile = new File(appDir); + if (!appDirFile.exists()) { + // if we have a bootstrap URL then we auto-create the app dir; this enables an + // installer to simply place a getdown.jar file somewhere and create an OS shortcut + // that runs getdown with an appdir and appbase specified, and have getdown create the + // appdir and download the app into it + if (!StringUtil.isBlank(appBase)) { + if (appDirFile.mkdirs()) { + notes.add(Note.info("Auto-created app directory '" + appDir + "'")); + } else { + notes.add(Note.warn("Unable to auto-create app dir: '" + appDir + "'")); + } + } else { + notes.add(Note.error("Invalid appdir '" + appDir + "': directory does not exist")); + return null; + } + } else if (!appDirFile.isDirectory()) { + notes.add(Note.error("Invalid appdir '" + appDir + "': refers to non-directory")); + return null; + } + + // pass along anything after the first two args as extra app args + List appArgs = argv.length > 2 ? + Arrays.asList(argv).subList(2, argv.length) : + Collections.emptyList(); + + // load X.509 certificate if it exists + File crtFile = new File(appDirFile, Digest.digestFile(Digest.VERSION) + ".crt"); + List certs = new ArrayList<>(); + if (crtFile.exists()) { + try (FileInputStream fis = new FileInputStream(crtFile)) { + X509Certificate certificate = (X509Certificate) + CertificateFactory.getInstance("X.509").generateCertificate(fis); + certs.add(certificate); + } catch (Exception e) { + notes.add(Note.error("Certificate error: " + e.getMessage())); + } + } + + return new EnvConfig(appDirFile, appId, appBase, certs, appArgs); + } + + /** The directory in which the application and metadata is stored. */ + public final File appDir; + + /** Either {@code null} or an identifier for a secondary application that should be + * launched. That app will use {@code appid.class} and {@code appid.apparg} to configure + * itself but all other parameters will be the same as the primary app. */ + public final String appId; + + /** Either {@code null} or fallback {@code appbase} to use if one cannot be read from a + * {@code getdown.txt} file during startup. */ + public final String appBase; + + /** Zero or more signing certificates used to verify the digest file. */ + public final List certs; + + /** Additional arguments to pass on to launched application. These will be added after the + * args in the getdown.txt file. */ + public final List appArgs; + + public EnvConfig (File appDir) { + this(appDir, null, null, Collections.emptyList(), + Collections.emptyList()); + } + + private EnvConfig (File appDir, String appId, String appBase, List certs, + List appArgs) { + this.appDir = appDir; + this.appId = appId; + this.appBase = appBase; + this.certs = certs; + this.appArgs = appArgs; + } + + private static final String USER_HOME_KEY = "${user.home}"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java new file mode 100644 index 0000000..b0a1dc9 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java @@ -0,0 +1,135 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.jar.JarFile; + +import com.threerings.getdown.cache.GarbageCollector; +import com.threerings.getdown.cache.ResourceCache; +import com.threerings.getdown.util.FileUtil; +import static com.threerings.getdown.Log.log; + +public class PathBuilder +{ + /** Name of directory to store cached code files in. */ + public static final String CODE_CACHE_DIR = ".cache"; + + /** Name of directory to store cached native resources in. */ + public static final String NATIVE_CACHE_DIR = ".ncache"; + + /** + * Builds either a default or cached classpath based on {@code app}'s configuration. + */ + public static ClassPath buildClassPath (Application app) throws IOException + { + return app.useCodeCache() ? buildCachedClassPath(app) : buildDefaultClassPath(app); + } + + /** + * Builds a {@link ClassPath} instance for {@code app} using the code resources in place in + * the app directory. + */ + public static ClassPath buildDefaultClassPath (Application app) + { + LinkedHashSet classPathEntries = new LinkedHashSet(); + for (Resource resource: app.getActiveCodeResources()) { + classPathEntries.add(resource.getFinalTarget()); + } + return new ClassPath(classPathEntries); + } + + /** + * Builds a {@link ClassPath} instance for {@code app} by first copying the code resources into + * a cache directory and then referencing them from there. This avoids problems with + * overwriting in-use classpath elements when the application is later updated. This also + * "garbage collects" expired caches if necessary. + */ + public static ClassPath buildCachedClassPath (Application app) throws IOException + { + File codeCacheDir = new File(app.getAppDir(), CODE_CACHE_DIR); + + // a negative value of code_cache_retention_days allows to clean up the cache forcefully + long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays()); + if (retainMillis != 0L) { + GarbageCollector.collect(codeCacheDir, retainMillis); + } + + ResourceCache cache = new ResourceCache(codeCacheDir); + LinkedHashSet classPathEntries = new LinkedHashSet<>(); + for (Resource resource: app.getActiveCodeResources()) { + String digest = app.getDigest(resource); + File entry = cache.cacheFile(resource.getFinalTarget(), digest.substring(0, 2), digest); + classPathEntries.add(entry); + } + + return new ClassPath(classPathEntries); + } + + /** + * Builds a {@link ClassPath} instance by first caching all native jars (indicated by + * nresource=[native jar]), unpacking them, and referencing the locations of each of the + * unpacked files. Also performs garbage collection similar to {@link #buildCachedClassPath} + * + * @param app used to determine native jars and related information. + * @param addCurrentLibraryPath if true, it adds the locations referenced by + * {@code System.getProperty("java.library.path")} as well. + * @return a classpath instance if at least one native resource was found and unpacked, + * {@code null} if no native resources were used by the application. + */ + public static ClassPath buildLibsPath (Application app, + boolean addCurrentLibraryPath) throws IOException { + List resources = app.getNativeResources(); + if (resources.isEmpty()) { + return null; + } + + LinkedHashSet nativedirs = new LinkedHashSet<>(); + File nativeCacheDir = new File(app.getAppDir(), NATIVE_CACHE_DIR); + ResourceCache cache = new ResourceCache(nativeCacheDir); + + // negative value forces total garbage collection, 0 avoids garbage collection at all + long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays()); + if (retainMillis != 0L) { + GarbageCollector.collectNative(nativeCacheDir, retainMillis); + } + + for (Resource resource : resources) { + // Use untruncated cache subdirectory names to avoid overwriting issues when unpacking, + // in the off chance that two native jars share a directory AND contain files with the + // same names + String digest = app.getDigest(resource); + File cachedFile = cache.cacheFile(resource.getFinalTarget(), digest, digest); + File cachedParent = cachedFile.getParentFile(); + File unpackedIndicator = new File(cachedParent, cachedFile.getName() + ".unpacked"); + + if (!unpackedIndicator.exists()) { + try { + FileUtil.unpackJar(new JarFile(cachedFile), cachedParent, false); + unpackedIndicator.createNewFile(); + } catch (IOException ioe) { + log.warning("Failed to unpack native jar", + "file", cachedFile.getAbsolutePath(), ioe); + // Keep going and unpack the other jars... + } + } + + nativedirs.add(cachedFile.getParentFile()); + } + + if (addCurrentLibraryPath) { + for (String path : System.getProperty("java.library.path").split(File.pathSeparator)) { + nativedirs.add(new File(path)); + } + } + + return new ClassPath(nativedirs); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java new file mode 100644 index 0000000..e70bd4b --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java @@ -0,0 +1,19 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +/** + * System property constants associated with Getdown. + */ +public class Properties +{ + /** This property will be set to "true" on the application when it is being run by getdown. */ + public static final String GETDOWN = "com.threerings.getdown"; + + /** If accepting connections from the launched application, this property + * will be set to the connection server port. */ + public static final String CONNECT_PORT = "com.threerings.getdown.connectPort"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java new file mode 100644 index 0000000..3e2f446 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java @@ -0,0 +1,394 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.*; +import java.net.URL; +import java.security.MessageDigest; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import com.threerings.getdown.util.FileUtil; +import com.threerings.getdown.util.ProgressObserver; +import com.threerings.getdown.util.StringUtil; + +import static com.threerings.getdown.Log.log; + +/** + * Models a single file resource used by an {@link Application}. + */ +public class Resource implements Comparable +{ + /** Defines special attributes for resources. */ + public static enum Attr { + /** Indicates that the resource should be unpacked. */ + UNPACK, + /** If present, when unpacking a resource, any directories created by the newly + * unpacked resource will first be cleared of files before unpacking. */ + CLEAN, + /** Indicates that the resource should be marked executable. */ + EXEC, + /** Indicates that the resource should be downloaded before a UI is displayed. */ + PRELOAD, + /** Indicates that the resource is a jar containing native libs. */ + NATIVE + }; + + public static final EnumSet NORMAL = EnumSet.noneOf(Attr.class); + public static final EnumSet UNPACK = EnumSet.of(Attr.UNPACK); + public static final EnumSet EXEC = EnumSet.of(Attr.EXEC); + public static final EnumSet PRELOAD = EnumSet.of(Attr.PRELOAD); + public static final EnumSet NATIVE = EnumSet.of(Attr.NATIVE); + + /** + * Computes the MD5 hash of the supplied file. + * @param version the version of the digest protocol to use. + */ + public static String computeDigest (int version, File target, MessageDigest md, + ProgressObserver obs) + throws IOException + { + md.reset(); + byte[] buffer = new byte[DIGEST_BUFFER_SIZE]; + int read; + + boolean isJar = isJar(target.getPath()); + boolean isPacked200Jar = isPacked200Jar(target.getPath()); + + // if this is a jar, we need to compute the digest in a "timestamp and file order" agnostic + // manner to properly correlate jardiff patched jars with their unpatched originals + if (isJar || isPacked200Jar){ + File tmpJarFile = null; + JarFile jar = null; + try { + // if this is a compressed jar file, uncompress it to compute the jar file digest + if (isPacked200Jar){ + tmpJarFile = new File(target.getPath() + ".tmp"); + FileUtil.unpackPacked200Jar(target, tmpJarFile); + jar = new JarFile(tmpJarFile); + } else{ + jar = new JarFile(target); + } + + List entries = Collections.list(jar.entries()); + Collections.sort(entries, ENTRY_COMP); + + int eidx = 0; + for (JarEntry entry : entries) { + // old versions of the digest code skipped metadata + if (version < 2) { + if (entry.getName().startsWith("META-INF")) { + updateProgress(obs, eidx, entries.size()); + continue; + } + } + + try (InputStream in = jar.getInputStream(entry)) { + while ((read = in.read(buffer)) != -1) { + md.update(buffer, 0, read); + } + } + + updateProgress(obs, eidx, entries.size()); + } + + } finally { + if (jar != null) { + try { + jar.close(); + } catch (IOException ioe) { + log.warning("Error closing jar", "path", target, "jar", jar, "error", ioe); + } + } + if (tmpJarFile != null) { + FileUtil.deleteHarder(tmpJarFile); + } + } + + } else { + long totalSize = target.length(), position = 0L; + try (FileInputStream fin = new FileInputStream(target)) { + while ((read = fin.read(buffer)) != -1) { + md.update(buffer, 0, read); + position += read; + updateProgress(obs, position, totalSize); + } + } + } + return StringUtil.hexlate(md.digest()); + } + + /** + * Creates a resource with the supplied remote URL and local path. + */ + public Resource (String path, URL remote, File local, EnumSet attrs) + { + _path = path; + _remote = remote; + _local = local; + _localNew = new File(local.toString() + "_new"); + String lpath = _local.getPath(); + _marker = new File(lpath + "v"); + + _attrs = attrs; + _isJar = isJar(lpath); + _isPacked200Jar = isPacked200Jar(lpath); + boolean unpack = attrs.contains(Attr.UNPACK); + if (unpack && _isJar) { + _unpacked = _local.getParentFile(); + } else if(unpack && _isPacked200Jar) { + String dotJar = ".jar", lname = _local.getName(); + String uname = lname.substring(0, lname.lastIndexOf(dotJar) + dotJar.length()); + _unpacked = new File(_local.getParent(), uname); + } + } + + /** + * Returns the path associated with this resource. + */ + public String getPath () + { + return _path; + } + + /** + * Returns the local location of this resource. + */ + public File getLocal () + { + return _local; + } + + /** + * Returns the location of the to-be-installed new version of this resource. + */ + public File getLocalNew () + { + return _localNew; + } + + /** + * Returns the location of the unpacked resource. + */ + public File getUnpacked () + { + return _unpacked; + } + + /** + * Returns the final target of this resource, whether it has been unpacked or not. + */ + public File getFinalTarget () + { + return shouldUnpack() ? getUnpacked() : getLocal(); + } + + /** + * Returns the remote location of this resource. + */ + public URL getRemote () + { + return _remote; + } + + /** + * Returns true if this resource should be unpacked as a part of the validation process. + */ + public boolean shouldUnpack () + { + return _attrs.contains(Attr.UNPACK) && !SysProps.noUnpack(); + } + + /** + * Returns true if this resource should be pre-downloaded. + */ + public boolean shouldPredownload () + { + return _attrs.contains(Attr.PRELOAD); + } + + /** + * Returns true if this resource is a native lib jar. + */ + public boolean isNative () + { + return _attrs.contains(Attr.NATIVE); + } + + /** + * Computes the MD5 hash of this resource's underlying file. + * Note: This is both CPU and I/O intensive. + * @param version the version of the digest protocol to use. + */ + public String computeDigest (int version, MessageDigest md, ProgressObserver obs) + throws IOException + { + File file; + if (_local.toString().toLowerCase(Locale.ROOT).endsWith(Application.CONFIG_FILE)) { + file = _local; + } else { + file = _localNew.exists() ? _localNew : _local; + } + return computeDigest(version, file, md, obs); + } + + /** + * Returns true if this resource has an associated "validated" marker + * file. + */ + public boolean isMarkedValid () + { + if (!_local.exists()) { + clearMarker(); + return false; + } + return _marker.exists(); + } + + /** + * Creates a "validated" marker file for this resource to indicate + * that its MD5 hash has been computed and compared with the value in + * the digest file. + * + * @throws IOException if we fail to create the marker file. + */ + public void markAsValid () + throws IOException + { + _marker.createNewFile(); + } + + /** + * Removes any "validated" marker file associated with this resource. + */ + public void clearMarker () + { + if (_marker.exists() && !FileUtil.deleteHarder(_marker)) { + log.warning("Failed to erase marker file '" + _marker + "'."); + } + } + + /** + * Installs the {@code getLocalNew} version of this resource to {@code getLocal}. + * @param validate whether or not to mark the resource as valid after installing. + */ + public void install (boolean validate) throws IOException { + File source = getLocalNew(), dest = getLocal(); + log.info("- " + source); + if (!FileUtil.renameTo(source, dest)) { + throw new IOException("Failed to rename " + source + " to " + dest); + } + applyAttrs(); + if (validate) { + markAsValid(); + } + } + + /** + * Unpacks this resource file into the directory that contains it. + */ + public void unpack () throws IOException + { + // sanity check + if (!_isJar && !_isPacked200Jar) { + throw new IOException("Requested to unpack non-jar file '" + _local + "'."); + } + if (_isJar) { + try (JarFile jar = new JarFile(_local)) { + FileUtil.unpackJar(jar, _unpacked, _attrs.contains(Attr.CLEAN)); + } + } else { + FileUtil.unpackPacked200Jar(_local, _unpacked); + } + } + + /** + * Applies this resources special attributes: unpacks this resource if needed, marks it as + * executable if needed. + */ + public void applyAttrs () throws IOException { + if (shouldUnpack()) { + unpack(); + } + if (_attrs.contains(Attr.EXEC)) { + FileUtil.makeExecutable(_local); + } + } + + /** + * Wipes this resource file along with any "validated" marker file that may be associated with + * it. + */ + public void erase () + { + clearMarker(); + if (_local.exists() && !FileUtil.deleteHarder(_local)) { + log.warning("Failed to erase resource '" + _local + "'."); + } + } + + @Override public int compareTo (Resource other) { + return _path.compareTo(other._path); + } + + @Override public boolean equals (Object other) + { + if (other instanceof Resource) { + return _path.equals(((Resource)other)._path); + } else { + return false; + } + } + + @Override public int hashCode () + { + return _path.hashCode(); + } + + @Override public String toString () + { + return _path; + } + + /** Helper function to simplify the process of reporting progress. */ + protected static void updateProgress (ProgressObserver obs, long pos, long total) + { + if (obs != null) { + obs.progress((int)(100 * pos / total)); + } + } + + protected static boolean isJar (String path) + { + return path.endsWith(".jar") || path.endsWith(".jar_new"); + } + + protected static boolean isPacked200Jar (String path) + { + return path.endsWith(".jar.pack") || path.endsWith(".jar.pack_new") || + path.endsWith(".jar.pack.gz")|| path.endsWith(".jar.pack.gz_new"); + } + + protected String _path; + protected URL _remote; + protected File _local, _localNew, _marker, _unpacked; + protected EnumSet _attrs; + protected boolean _isJar, _isPacked200Jar; + + /** Used to sort the entries in a jar file. */ + protected static final Comparator ENTRY_COMP = new Comparator() { + @Override public int compare (JarEntry e1, JarEntry e2) { + return e1.getName().compareTo(e2.getName()); + } + }; + + protected static final int DIGEST_BUFFER_SIZE = 5 * 1025; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java new file mode 100644 index 0000000..0d96ecb --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java @@ -0,0 +1,185 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.threerings.getdown.util.VersionUtil; + +/** + * This class encapsulates all system properties that are read and processed by Getdown. Don't + * stick a call to {@code System.getProperty} randomly into the code, put it in here and give it an + * accessor so that it's easy to see all of the secret system property arguments that Getdown makes + * use of. + */ +public class SysProps +{ + /** Configures the appdir (in lieu of passing it in argv). Usage: {@code -Dappdir=foo}. */ + public static String appDir () { + return System.getProperty("appdir"); + } + + /** Configures the appid (in lieu of passing it in argv). Usage: {@code -Dappid=foo}. */ + public static String appId () { + return System.getProperty("appid"); + } + + /** Configures the bootstrap appbase (used in lieu of providing a skeleton getdown.txt, and as + * a last resort fallback). Usage: {@code -Dappbase=URL}. */ + public static String appBase () { + return System.getProperty("appbase"); + } + + /** If true, disables redirection of logging into {@code launcher.log}. + * Usage: {@code -Dno_log_redir}. */ + public static boolean noLogRedir () { + return System.getProperty("no_log_redir") != null; + } + + /** Overrides the domain on {@code appbase}. Usage: {@code -Dappbase_domain=foo}. */ + public static String appbaseDomain () { + return System.getProperty("appbase_domain"); + } + + /** Overrides enter {@code appbase}. Usage: {@code -Dappbase_override=URL}. */ + public static String appbaseOverride () { + return System.getProperty("appbase_override"); + } + + /** If true, Getdown installs the app without ever bringing up a UI (except in the event of an + * error). NOTE: it does not launch the app. See {@link #launchInSilent}. + * Usage: {@code -Dsilent}. */ + public static boolean silent () { + return System.getProperty("silent") != null; + } + + /** Instructs Getdown to install/update the app without ever bringing up a UI (except in the + * event of an error), and then launch it. + * Usage: {@code -Dsilent=launch}. */ + public static boolean launchInSilent () { + return "launch".equals(System.getProperty("silent")); + } + + /** + * Instructs Getdown to launch the app without updating it, or ever bringing up a UI (except + * in the event of an error). + * Usage: {@code -Dsilent=noupdate}. + */ + public static boolean noUpdate() { + return "noupdate".equals(System.getProperty("silent")); + } + + /** If true, Getdown does not automatically install updates after downloading them. It waits + * for the application to call `Getdown.install`. + * Usage: {@code -Dno_install}. */ + public static boolean noInstall () { + return System.getProperty("no_install") != null; + } + + /** Specifies the delay (in minutes) to wait before starting the update and install process. + * Minimum delay is 0 minutes, or no delay (negative values are rounded up to 0 minutes). + * Maximum delay is 1 day, or 1440 minutes (larger values are rounded down to 1 day). + * Usage: {@code -Ddelay=N}. */ + public static int startDelay () { + return Math.min(Math.max(Integer.getInteger("delay", 0), 0), 60 * 24); + } + + /** If true, Getdown will not unpack {@code uresource} jars. Usage: {@code -Dno_unpack}. */ + public static boolean noUnpack () { + return Boolean.getBoolean("no_unpack"); + } + + /** If true, Getdown will run the application in the same VM in which Getdown is running. If + * false (the default), Getdown will fork a new VM. Note that reusing the same VM prevents + * Getdown from configuring some launch-time-only VM parameters (like -mxN etc.). + * Usage: {@code -Ddirect}. */ + public static boolean direct () { + return Boolean.getBoolean("direct"); + } + + /** Specifies the connection timeout (in seconds) to use when downloading control files from + * the server. This is chiefly useful when you are running in versionless mode and want Getdown + * to more quickly timeout its startup update check if the server with which it is + * communicating is not available. Usage: {@code -Dconnect_timeout=N}. */ + public static int connectTimeout () { + return Integer.getInteger("connect_timeout", 0); + } + + /** Specifies the read timeout (in seconds) to use when downloading all files from the server. + * The default is 30 seconds, meaning that if a download stalls for more than 30 seconds, the + * update process wil fail. Setting the timeout to zero (or a negative value) will disable it. + * Usage: {@code -Dread_timeout=N}. */ + public static int readTimeout () { + return Integer.getInteger("read_timeout", 30); + } + + /** Returns the number of threads used to perform digesting and verifying operations in + * parallel. Usage: {@code -Dthread_pool_size=N} */ + public static int threadPoolSize () { + int defaultSize = Math.max(Runtime.getRuntime().availableProcessors()-1, 1); + return Integer.getInteger("thread_pool_size", defaultSize); + } + + /** Parses a Java version system property using the supplied regular expression. The numbers + * extracted from the regexp will be placed in each consecutive hundreds position in the + * returned value. + * + *

For example, {@code java.version} takes the form {@code 1.8.0_31}, and with the regexp + * {@code (\d+)\.(\d+)\.(\d+)(_\d+)?} we would parse {@code 1, 8, 0, 31} and combine them into + * the final value {@code 1080031}. + * + *

Note that non-numeric characters matched by the regular expression will simply be + * ignored, and optional groups which do not match are treated as zero in the final version + * calculation. + * + *

One can instead parse {@code java.runtime.version} which takes the form {@code + * 1.8.0_31-b13}. Using regexp {@code (\d+)\.(\d+)\.(\d+)_(\d+)-b(\d+)} we would parse + * {@code 1, 8, 0, 31, 13} and combine them into a final value {@code 108003113}. + * + *

Other (or future) JVMs may provide different version properties which can be parsed as + * desired using this general scheme as long as the numbers appear from left to right in order + * of significance. + * + * @throws IllegalArgumentException if no system named {@code propName} exists, or if + * {@code propRegex} does not match the returned version string. + */ + public static long parseJavaVersion (String propName, String propRegex) { + String verstr = System.getProperty(propName); + if (verstr == null) throw new IllegalArgumentException( + "No system property '" + propName + "'."); + + long vers = VersionUtil.parseJavaVersion(propRegex, verstr); + if (vers == 0L) throw new IllegalArgumentException( + "Regexp '" + propRegex + "' does not match '" + verstr + "' (from " + propName + ")"); + return vers; + } + + /** + * Applies {@code appbase_override} or {@code appbase_domain} if they are set. + */ + public static String overrideAppbase (String appbase) { + String appbaseOverride = appbaseOverride(); + if (appbaseOverride != null) { + return appbaseOverride; + } else { + return replaceDomain(appbase); + } + } + + /** + * If appbase_domain property is set, replace the domain on the provided string. + */ + public static String replaceDomain (String appbase) + { + String appbaseDomain = appbaseDomain(); + if (appbaseDomain != null) { + Matcher m = Pattern.compile("(https?://[^/]+)(.*)").matcher(appbase); + appbase = m.replaceAll(appbaseDomain + "$2"); + } + return appbase; + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java new file mode 100644 index 0000000..6033e2f --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java @@ -0,0 +1,229 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.net; + +import java.io.File; +import java.io.IOException; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import com.threerings.getdown.data.Resource; + +import static com.threerings.getdown.Log.log; + +/** + * Handles the download of a collection of files, first issuing HTTP head requests to obtain size + * information and then downloading the files individually, reporting progress back via protected + * callback methods. Note: these methods are all called arbitrary download threads, so + * implementors must take care to only execute thread-safe code or simply pass a message to the AWT + * thread, for example. + */ +public abstract class Downloader +{ + /** + * Start the downloading process. + * @param resources the resources to download. + * @param maxConcurrent the maximum number of concurrent downloads allowed. + * @return true if the download completed, false if it was aborted (via {@link #abort}). + */ + public boolean download (Collection resources, int maxConcurrent) + { + // first compute the total size of our download + resolvingDownloads(); + for (Resource rsrc : resources) { + try { + _sizes.put(rsrc, Math.max(checkSize(rsrc), 0L)); + } catch (IOException ioe) { + downloadFailed(rsrc, ioe); + } + } + + long totalSize = sum(_sizes.values()); + log.info("Downloading " + resources.size() + " resources", + "totalBytes", totalSize, "maxConcurrent", maxConcurrent); + + // make a note of the time at which we started the download + _start = System.currentTimeMillis(); + + // start the downloads + ExecutorService exec = Executors.newFixedThreadPool(maxConcurrent); + for (final Resource rsrc : resources) { + // make sure the resource's target directory exists + File parent = new File(rsrc.getLocal().getParent()); + if (!parent.exists() && !parent.mkdirs()) { + log.warning("Failed to create target directory for resource '" + rsrc + "'."); + } + + exec.execute(new Runnable() { + @Override public void run () { + try { + if (_state != State.ABORTED) { + download(rsrc); + } + } catch (IOException ioe) { + _state = State.FAILED; + downloadFailed(rsrc, ioe); + } + } + }); + } + exec.shutdown(); + + // wait for the downloads to complete + try { + exec.awaitTermination(10, TimeUnit.DAYS); + + // report download completion if we did not already do so via our final resource + if (_state == State.DOWNLOADING) { + downloadProgress(100, 0); + } + + } catch (InterruptedException ie) { + exec.shutdownNow(); + downloadFailed(null, ie); + } + + return _state != State.ABORTED; + } + + /** + * Aborts the in-progress download. + */ + public void abort () { + _state = State.ABORTED; + } + + /** + * Called before the downloader begins the series of HTTP head requests to determine the + * size of the files it needs to download. + */ + protected void resolvingDownloads () {} + + /** + * Reports ongoing progress toward completion of the overall downloading task. One call is + * guaranteed to be made reporting 100% completion if the download is not aborted and no + * resources fail. + * + * @param percent the percent completion of the complete download process (based on total bytes + * downloaded versus total byte size of all resources). + * @param remaining the estimated download time remaining in seconds, or {@code -1} if the time + * can not yet be determined. + */ + protected void downloadProgress (int percent, long remaining) {} + + /** + * Called if a failure occurs while downloading a resource. No progress will be reported after + * a download fails, but additional download failures may be reported. + * + * @param rsrc the resource that failed to download, or null if the download failed due to + * thread interruption. + * @param cause the exception detailing the failure. + */ + protected void downloadFailed (Resource rsrc, Exception cause) {} + + /** + * Performs the protocol-specific portion of checking download size. + */ + protected abstract long checkSize (Resource rsrc) throws IOException; + + /** + * Periodically called by the protocol-specific downloaders to update their progress. This + * should be called at least once for each resource to be downloaded, with the total downloaded + * size for that resource. It can also be called periodically along the way for each resource + * to communicate incremental progress. + * + * @param rsrc the resource currently being downloaded. + * @param currentSize the number of bytes currently downloaded for said resource. + * @param actualSize the size reported for this resource now that we're actually downloading + * it. Some web servers lie about Content-length when doing a HEAD request, so by reporting + * updated sizes here we can recover from receiving bogus information in the earlier + * {@link #checkSize} phase. + */ + protected synchronized void reportProgress (Resource rsrc, long currentSize, long actualSize) + { + // update the actual size for this resource (but don't let it shrink) + _sizes.put(rsrc, actualSize = Math.max(actualSize, _sizes.get(rsrc))); + + // update the current downloaded size for said resource; don't allow the downloaded bytes + // to exceed the original claimed size of the resource, otherwise our progress will get + // booched and we'll end up back on the Daily WTF: http://tinyurl.com/29wt4oq + _downloaded.put(rsrc, Math.min(actualSize, currentSize)); + + // notify the observer if it's been sufficiently long since our last notification + long now = System.currentTimeMillis(); + if ((now - _lastUpdate) >= UPDATE_DELAY) { + _lastUpdate = now; + + // total up our current and total bytes + long downloaded = sum(_downloaded.values()); + long totalSize = sum(_sizes.values()); + + // compute our bytes per second + long secs = (now - _start) / 1000L; + long bps = (secs == 0) ? 0 : (downloaded / secs); + + // compute our percentage completion + int pctdone = (totalSize == 0) ? 0 : (int)((downloaded * 100f) / totalSize); + + // estimate our time remaining + long remaining = (bps <= 0 || totalSize == 0) ? -1 : (totalSize - downloaded) / bps; + + // if we're complete or failed, when we don't want to report again + if (_state == State.DOWNLOADING) { + if (pctdone == 100) _state = State.COMPLETE; + downloadProgress(pctdone, remaining); + } + } + } + + /** + * Sums the supplied values. + */ + protected static long sum (Iterable values) + { + long acc = 0L; + for (Long value : values) { + acc += value; + } + return acc; + } + + protected enum State { DOWNLOADING, COMPLETE, FAILED, ABORTED } + + /** + * Accomplishes the copying of the resource from remote location to local location using + * protocol-specific code. This method should periodically check whether {@code _state} is set + * to aborted and abort any in-progress download if so. + */ + protected abstract void download (Resource rsrc) throws IOException; + + /** The reported sizes of our resources. */ + protected Map _sizes = new HashMap<>(); + + /** The bytes downloaded for each resource. */ + protected Map _downloaded = new HashMap<>(); + + /** The time at which the file transfer began. */ + protected long _start; + + /** The current transfer rate in bytes per second. */ + protected long _bytesPerSecond; + + /** The time at which the last progress update was posted to the progress observer. */ + protected long _lastUpdate; + + /** A wee state machine to ensure we call our callbacks sanely. */ + protected volatile State _state = State.DOWNLOADING; + + /** The delay in milliseconds between notifying progress observers of file download + * progress. */ + protected static final long UPDATE_DELAY = 500L; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java new file mode 100644 index 0000000..a7a3287 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java @@ -0,0 +1,115 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.net; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; + +import com.threerings.getdown.data.Resource; +import com.threerings.getdown.util.ConnectionUtil; + +import static com.threerings.getdown.Log.log; + +/** + * Implements downloading files over HTTP + */ +public class HTTPDownloader extends Downloader +{ + public HTTPDownloader (Proxy proxy) + { + _proxy = proxy; + } + + @Override protected long checkSize (Resource rsrc) throws IOException + { + URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0); + try { + // if we're accessing our data via HTTP, we only need a HEAD request + if (conn instanceof HttpURLConnection) { + HttpURLConnection hcon = (HttpURLConnection)conn; + hcon.setRequestMethod("HEAD"); + hcon.connect(); + // make sure we got a satisfactory response code + if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("Unable to check up-to-date for " + + rsrc.getRemote() + ": " + hcon.getResponseCode()); + } + } + return conn.getContentLength(); + + } finally { + // let it be known that we're done with this connection + conn.getInputStream().close(); + } + } + + @Override protected void download (Resource rsrc) throws IOException + { + // TODO: make FileChannel download impl (below) robust and allow apps to opt-into it via a + // system property + if (true) { + // download the resource from the specified URL + URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0); + conn.connect(); + + // make sure we got a satisfactory response code + if (conn instanceof HttpURLConnection) { + HttpURLConnection hcon = (HttpURLConnection)conn; + if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("Unable to download resource " + rsrc.getRemote() + ": " + + hcon.getResponseCode()); + } + } + long actualSize = conn.getContentLength(); + log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize); + long currentSize = 0L; + byte[] buffer = new byte[4*4096]; + try (InputStream in = conn.getInputStream(); + FileOutputStream out = new FileOutputStream(rsrc.getLocalNew())) { + + // TODO: look to see if we have a download info file + // containing info on potentially partially downloaded data; + // if so, use a "Range: bytes=HAVE-" header. + + // read in the file data + int read; + while ((read = in.read(buffer)) != -1) { + // abort the download if the downloader is aborted + if (_state == State.ABORTED) { + break; + } + // write it out to our local copy + out.write(buffer, 0, read); + // note that we've downloaded some data + currentSize += read; + reportProgress(rsrc, currentSize, actualSize); + } + } + + } else { + log.info("Downloading resource", "url", rsrc.getRemote(), "size", "unknown"); + File localNew = rsrc.getLocalNew(); + try (ReadableByteChannel rbc = Channels.newChannel(rsrc.getRemote().openStream()); + FileOutputStream fos = new FileOutputStream(localNew)) { + // TODO: more work is needed here, transferFrom can fail to transfer the entire + // file, in which case it's not clear what we're supposed to do.. call it again? + // will it repeatedly fail? + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + reportProgress(rsrc, localNew.length(), localNew.length()); + } + } + } + + protected final Proxy _proxy; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java new file mode 100644 index 0000000..22446ec --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java @@ -0,0 +1,32 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.spi; + +/** + * A service provider interface that handles the storage of proxy credentials. + */ +public interface ProxyAuth +{ + /** Credentials for a proxy server. */ + public static class Credentials { + public final String username; + public final String password; + public Credentials (String username, String password) { + this.username = username; + this.password = password; + } + } + + /** + * Loads the credentials for the app installed in {@code appDir}. + */ + public Credentials loadCredentials (String appDir); + + /** + * Encrypts and saves the credentials for the app installed in {@code appDir}. + */ + public void saveCredentials (String appDir, String username, String password); +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java new file mode 100644 index 0000000..c2e740b --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java @@ -0,0 +1,232 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import java.security.MessageDigest; + +import com.threerings.getdown.data.Application; +import com.threerings.getdown.data.Digest; +import com.threerings.getdown.data.EnvConfig; +import com.threerings.getdown.data.Resource; +import com.threerings.getdown.util.FileUtil; +import com.threerings.getdown.util.StreamUtil; + +/** + * Generates patch files between two particular revisions of an + * application. The differences between all the files in the two + * revisions are bundled into a single patch file which is placed into the + * target version directory. + */ +public class Differ +{ + /** + * Creates a single patch file that contains the differences between + * the two specified application directories. The patch file will be + * created in the nvdir directory with name + * patchV.dat where V is the old application version. + */ + public void createDiff (File nvdir, File ovdir, boolean verbose) + throws IOException + { + // sanity check + String nvers = nvdir.getName(); + String overs = ovdir.getName(); + try { + if (Long.parseLong(nvers) <= Long.parseLong(overs)) { + String err = "New version (" + nvers + ") must be greater " + + "than old version (" + overs + ")."; + throw new IOException(err); + } + } catch (NumberFormatException nfe) { + throw new IOException("Non-numeric versions? [nvers=" + nvers + + ", overs=" + overs + "]."); + } + + Application oapp = new Application(new EnvConfig(ovdir)); + oapp.init(false); + ArrayList orsrcs = new ArrayList<>(); + orsrcs.addAll(oapp.getCodeResources()); + orsrcs.addAll(oapp.getResources()); + + Application napp = new Application(new EnvConfig(nvdir)); + napp.init(false); + ArrayList nrsrcs = new ArrayList<>(); + nrsrcs.addAll(napp.getCodeResources()); + nrsrcs.addAll(napp.getResources()); + + // first create a patch for the main application + File patch = new File(nvdir, "patch" + overs + ".dat"); + createPatch(patch, orsrcs, nrsrcs, verbose); + + // next create patches for any auxiliary resource groups + for (Application.AuxGroup ag : napp.getAuxGroups()) { + orsrcs = new ArrayList<>(); + Application.AuxGroup oag = oapp.getAuxGroup(ag.name); + if (oag != null) { + orsrcs.addAll(oag.codes); + orsrcs.addAll(oag.rsrcs); + } + nrsrcs = new ArrayList<>(); + nrsrcs.addAll(ag.codes); + nrsrcs.addAll(ag.rsrcs); + patch = new File(nvdir, "patch-" + ag.name + overs + ".dat"); + createPatch(patch, orsrcs, nrsrcs, verbose); + } + } + + protected void createPatch (File patch, ArrayList orsrcs, + ArrayList nrsrcs, boolean verbose) + throws IOException + { + int version = Digest.VERSION; + MessageDigest md = Digest.getMessageDigest(version); + try (FileOutputStream fos = new FileOutputStream(patch); + BufferedOutputStream buffered = new BufferedOutputStream(fos); + JarOutputStream jout = new JarOutputStream(buffered)) { + + // for each file in the new application, it either already exists + // in the old application, or it is new + for (Resource rsrc : nrsrcs) { + int oidx = orsrcs.indexOf(rsrc); + Resource orsrc = (oidx == -1) ? null : orsrcs.remove(oidx); + if (orsrc != null) { + // first see if they are the same + String odig = orsrc.computeDigest(version, md, null); + String ndig = rsrc.computeDigest(version, md, null); + if (odig.equals(ndig)) { + if (verbose) { + System.out.println("Unchanged: " + rsrc.getPath()); + } + // by leaving it out, it will be left as is during the + // patching process + continue; + } + + // otherwise potentially create a jar diff + if (rsrc.getPath().endsWith(".jar")) { + if (verbose) { + System.out.println("JarDiff: " + rsrc.getPath()); + } + // here's a juicy one: JarDiff blindly pulls ZipEntry + // objects out of one jar file and stuffs them into + // another without clearing out things like the + // compressed size, so if, for whatever reason (like + // different JRE versions or phase of the moon) the + // compressed size in the old jar file is different + // than the compressed size generated when creating the + // jardiff jar file, ZipOutputStream will choke and + // we'll be hosed; so we recreate the jar files in + // their entirety before running jardiff on 'em + File otemp = rebuildJar(orsrc.getLocal()); + File temp = rebuildJar(rsrc.getLocal()); + jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.PATCH)); + jarDiff(otemp, temp, jout); + FileUtil.deleteHarder(otemp); + FileUtil.deleteHarder(temp); + continue; + } + } + + if (verbose) { + System.out.println("Addition: " + rsrc.getPath()); + } + jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.CREATE)); + pipe(rsrc.getLocal(), jout); + } + + // now any file remaining in orsrcs needs to be removed + for (Resource rsrc : orsrcs) { + // add an entry with the resource name and the deletion suffix + if (verbose) { + System.out.println("Removal: " + rsrc.getPath()); + } + jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.DELETE)); + } + + System.out.println("Created patch file: " + patch); + + } catch (IOException ioe) { + FileUtil.deleteHarder(patch); + throw ioe; + } + } + + protected File rebuildJar (File target) + throws IOException + { + File temp = File.createTempFile("differ", "jar"); + try (JarFile jar = new JarFile(target); + FileOutputStream tempFos = new FileOutputStream(temp); + BufferedOutputStream tempBos = new BufferedOutputStream(tempFos); + JarOutputStream jout = new JarOutputStream(tempBos)) { + byte[] buffer = new byte[4096]; + for (Enumeration< JarEntry > iter = jar.entries(); iter.hasMoreElements();) { + JarEntry entry = iter.nextElement(); + entry.setCompressedSize(-1); + jout.putNextEntry(entry); + try (InputStream in = jar.getInputStream(entry)) { + int size = in.read(buffer); + while (size != -1) { + jout.write(buffer, 0, size); + size = in.read(buffer); + } + } + } + } + return temp; + } + + protected void jarDiff (File ofile, File nfile, JarOutputStream jout) + throws IOException + { + JarDiff.createPatch(ofile.getPath(), nfile.getPath(), jout, false); + } + + public static void main (String[] args) + { + if (args.length < 2) { + System.err.println( + "Usage: Differ [-verbose] new_vers_dir old_vers_dir"); + System.exit(255); + } + Differ differ = new Differ(); + boolean verbose = false; + int aidx = 0; + if (args[0].equals("-verbose")) { + verbose = true; + aidx++; + } + try { + differ.createDiff(new File(args[aidx++]), + new File(args[aidx++]), verbose); + } catch (IOException ioe) { + System.err.println("Error: " + ioe.getMessage()); + System.exit(255); + } + } + + protected static void pipe (File file, JarOutputStream jout) + throws IOException + { + try (FileInputStream fin = new FileInputStream(file)) { + StreamUtil.copy(fin, jout); + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java new file mode 100644 index 0000000..b04a653 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java @@ -0,0 +1,129 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.Signature; + +import java.util.ArrayList; +import java.util.List; + +import com.threerings.getdown.data.Application; +import com.threerings.getdown.data.Digest; +import com.threerings.getdown.data.EnvConfig; +import com.threerings.getdown.data.Resource; +import com.threerings.getdown.util.Base64; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Handles the generation of the digest.txt file. + */ +public class Digester +{ + /** + * A command line entry point for the digester. + */ + public static void main (String[] args) + throws IOException, GeneralSecurityException + { + switch (args.length) { + case 1: + createDigests(new File(args[0]), null, null, null); + break; + case 4: + createDigests(new File(args[0]), new File(args[1]), args[2], args[3]); + break; + default: + System.err.println("Usage: Digester app_dir [keystore_path password alias]"); + System.exit(255); + } + } + + /** + * Creates digest file(s) and optionally signs them if {@code keystore} is not null. + */ + public static void createDigests (File appdir, File keystore, String password, String alias) + throws IOException, GeneralSecurityException + { + for (int version = 1; version <= Digest.VERSION; version++) { + createDigest(version, appdir); + if (keystore != null) { + signDigest(version, appdir, keystore, password, alias); + } + } + } + + /** + * Creates a digest file in the specified application directory. + */ + public static void createDigest (int version, File appdir) + throws IOException + { + File target = new File(appdir, Digest.digestFile(version)); + System.out.println("Generating digest file '" + target + "'..."); + + // create our application and instruct it to parse its business + Application app = new Application(new EnvConfig(appdir)); + app.init(false); + + List rsrcs = new ArrayList<>(); + rsrcs.add(app.getConfigResource()); + rsrcs.addAll(app.getCodeResources()); + rsrcs.addAll(app.getResources()); + for (Application.AuxGroup ag : app.getAuxGroups()) { + rsrcs.addAll(ag.codes); + rsrcs.addAll(ag.rsrcs); + } + + // now generate the digest file + Digest.createDigest(version, rsrcs, target); + } + + /** + * Creates a digest file in the specified application directory. + */ + public static void signDigest (int version, File appdir, + File storePath, String storePass, String storeAlias) + throws IOException, GeneralSecurityException + { + String filename = Digest.digestFile(version); + File inputFile = new File(appdir, filename); + File signatureFile = new File(appdir, filename + Application.SIGNATURE_SUFFIX); + + try (FileInputStream storeInput = new FileInputStream(storePath); + FileInputStream dataInput = new FileInputStream(inputFile); + FileOutputStream signatureOutput = new FileOutputStream(signatureFile)) { + + // initialize the keystore + KeyStore store = KeyStore.getInstance("JKS"); + store.load(storeInput, storePass.toCharArray()); + PrivateKey key = (PrivateKey)store.getKey(storeAlias, storePass.toCharArray()); + + // sign the digest file + String algo = Digest.sigAlgorithm(version); + Signature sig = Signature.getInstance(algo); + byte[] buffer = new byte[8192]; + int length; + + sig.initSign(key); + while ((length = dataInput.read(buffer)) != -1) { + sig.update(buffer, 0, length); + } + + // Write out the signature + String signed = Base64.encodeToString(sig.sign(), Base64.DEFAULT); + signatureOutput.write(signed.getBytes(UTF_8)); + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java new file mode 100644 index 0000000..1cea0ea --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java @@ -0,0 +1,449 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +/* + * @(#)JarDiff.java 1.7 05/11/17 + * + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * -Redistribution of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") + * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE + * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or intended + * for use in the design, construction, operation or maintenance of any + * nuclear facility. + */ + +package com.threerings.getdown.tools; + +import java.io.*; +import java.util.*; +import java.util.jar.*; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * JarDiff is able to create a jar file containing the delta between two jar files (old and new). + * The delta jar file can then be applied to the old jar file to reconstruct the new jar file. + * + *

Refer to the JNLP spec for details on how this is done. + * + * @version 1.13, 06/26/03 + */ +public class JarDiff implements JarDiffCodes +{ + private static final int DEFAULT_READ_SIZE = 2048; + private static byte[] newBytes = new byte[DEFAULT_READ_SIZE]; + private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE]; + + // The JARDiff.java is the stand-alone jardiff.jar tool. Thus, we do not depend on Globals.java + // and other stuff here. Instead, we use an explicit _debug flag. + private static boolean _debug; + + /** + * Creates a patch from the two passed in files, writing the result to os. + */ + public static void createPatch (String oldPath, String newPath, + OutputStream os, boolean minimal) throws IOException + { + try (JarFile2 oldJar = new JarFile2(oldPath); + JarFile2 newJar = new JarFile2(newPath)) { + + HashMap moved = new HashMap<>(); + HashSet implicit = new HashSet<>(); + HashSet moveSrc = new HashSet<>(); + HashSet newEntries = new HashSet<>(); + + // FIRST PASS + // Go through the entries in new jar and + // determine which files are candidates for implicit moves + // ( files that has the same filename and same content in old.jar + // and new.jar ) + // and for files that cannot be implicitly moved, we will either + // find out whether it is moved or new (modified) + for (JarEntry newEntry : newJar) { + String newname = newEntry.getName(); + + // Return best match of contents, will return a name match if possible + String oldname = oldJar.getBestMatch(newJar, newEntry); + if (oldname == null) { + // New or modified entry + if (_debug) { + System.out.println("NEW: "+ newname); + } + newEntries.add(newname); + } else { + // Content already exist - need to do a move + + // Should do implicit move? Yes, if names are the same, and + // no move command already exist from oldJar + if (oldname.equals(newname) && !moveSrc.contains(oldname)) { + if (_debug) { + System.out.println(newname + " added to implicit set!"); + } + implicit.add(newname); + } else { + // The 1.0.1/1.0 JarDiffPatcher cannot handle + // multiple MOVE command with same src. + // The work around here is if we are going to generate + // a MOVE command with duplicate src, we will + // instead add the target as a new file. This way + // the jardiff can be applied by 1.0.1/1.0 + // JarDiffPatcher also. + if (!minimal && (implicit.contains(oldname) || + moveSrc.contains(oldname) )) { + + // generate non-minimal jardiff + // for backward compatibility + + if (_debug) { + + System.out.println("NEW: "+ newname); + } + newEntries.add(newname); + } else { + // Use newname as key, since they are unique + if (_debug) { + System.err.println("moved.put " + newname + " " + oldname); + } + moved.put(newname, oldname); + moveSrc.add(oldname); + } + // Check if this disables an implicit 'move ' + if (implicit.contains(oldname) && minimal) { + + if (_debug) { + System.err.println("implicit.remove " + oldname); + + System.err.println("moved.put " + oldname + " " + oldname); + + } + implicit.remove(oldname); + moved.put(oldname, oldname); + moveSrc.add(oldname); + } + } + } + } + + // SECOND PASS: = - - + // - + ArrayList deleted = new ArrayList<>(); + for (JarEntry oldEntry : oldJar) { + String oldName = oldEntry.getName(); + if (!implicit.contains(oldName) && !moveSrc.contains(oldName) + && !newEntries.contains(oldName)) { + if (_debug) { + System.err.println("deleted.add " + oldName); + } + deleted.add(oldName); + } + } + + //DEBUG + if (_debug) { + //DEBUG: print out moved map + System.out.println("MOVED MAP!!!"); + for (Map.Entry entry : moved.entrySet()) { + System.out.println(entry); + } + + //DEBUG: print out IMOVE map + System.out.println("IMOVE MAP!!!"); + for (String newName : implicit) { + System.out.println("key is " + newName); + } + } + + JarOutputStream jos = new JarOutputStream(os); + + // Write out all the MOVEs and REMOVEs + createIndex(jos, deleted, moved); + + // Put in New and Modified entries + for (String newName : newEntries) { + if (_debug) { + System.out.println("New File: " + newName); + } + writeEntry(jos, newJar.getEntryByName(newName), newJar); + } + + jos.finish(); +// jos.close(); + } + } + + /** + * Writes the index file out to jos. + * oldEntries gives the names of the files that were removed, + * movedMap maps from the new name to the old name. + */ + private static void createIndex (JarOutputStream jos, List oldEntries, + Map movedMap) + throws IOException + { + StringWriter writer = new StringWriter(); + writer.write(VERSION_HEADER); + writer.write("\r\n"); + + // Write out entries that have been removed + for (String name : oldEntries) { + writer.write(REMOVE_COMMAND); + writer.write(" "); + writeEscapedString(writer, name); + writer.write("\r\n"); + } + + // And those that have moved + for (String newName : movedMap.keySet()) { + String oldName = movedMap.get(newName); + writer.write(MOVE_COMMAND); + writer.write(" "); + writeEscapedString(writer, oldName); + writer.write(" "); + writeEscapedString(writer, newName); + writer.write("\r\n"); + } + + jos.putNextEntry(new JarEntry(INDEX_NAME)); + byte[] bytes = writer.toString().getBytes(UTF_8); + jos.write(bytes, 0, bytes.length); + } + + protected static Writer writeEscapedString (Writer writer, String string) + throws IOException + { + int index = 0; + int last = 0; + char[] chars = null; + + while ((index = string.indexOf(' ', index)) != -1) { + if (last != index) { + if (chars == null) { + chars = string.toCharArray(); + } + writer.write(chars, last, index - last); + } + last = index; + index++; + writer.write('\\'); + } + if (last != 0 && chars != null) { + writer.write(chars, last, chars.length - last); + } + else { + // no spaces + writer.write(string); + } + + return writer; + } + + private static void writeEntry (JarOutputStream jos, JarEntry entry, JarFile2 file) + throws IOException + { + try (InputStream data = file.getJarFile().getInputStream(entry)) { + jos.putNextEntry(entry); + int size = data.read(newBytes); + while (size != -1) { + jos.write(newBytes, 0, size); + size = data.read(newBytes); + } + } + } + + /** + * JarFile2 wraps a JarFile providing some convenience methods. + */ + private static class JarFile2 implements Iterable, Closeable + { + private JarFile _jar; + private List _entries; + private HashMap _nameToEntryMap; + private HashMap> _crcToEntryMap; + + public JarFile2 (String path) throws IOException { + _jar = new JarFile(new File(path)); + index(); + } + + public JarFile getJarFile () { + return _jar; + } + + // from interface Iterable + @Override + public Iterator iterator () { + return _entries.iterator(); + } + + public JarEntry getEntryByName (String name) { + return _nameToEntryMap.get(name); + } + + /** + * Returns true if the two InputStreams differ. + */ + private static boolean differs (InputStream oldIS, InputStream newIS) throws IOException { + int newSize = 0; + int oldSize; + int total = 0; + boolean retVal = false; + + while (newSize != -1) { + newSize = newIS.read(newBytes); + oldSize = oldIS.read(oldBytes); + + if (newSize != oldSize) { + if (_debug) { + System.out.println("\tread sizes differ: " + newSize + + " " + oldSize + " total " + total); + } + retVal = true; + break; + } + if (newSize > 0) { + while (--newSize >= 0) { + total++; + if (newBytes[newSize] != oldBytes[newSize]) { + if (_debug) { + System.out.println("\tbytes differ at " + + total); + } + retVal = true; + break; + } + if ( retVal ) { + //Jump out + break; + } + newSize = 0; + } + } + } + + return retVal; + } + + public String getBestMatch (JarFile2 file, JarEntry entry) throws IOException { + // check for same name and same content, return name if found + if (contains(file, entry)) { + return (entry.getName()); + } + + // return name of same content file or null + return (hasSameContent(file,entry)); + } + + public boolean contains (JarFile2 f, JarEntry e) throws IOException { + + JarEntry thisEntry = getEntryByName(e.getName()); + + // Look up name in 'this' Jar2File - if not exist return false + if (thisEntry == null) + return false; + + // Check CRC - if no match - return false + if (thisEntry.getCrc() != e.getCrc()) + return false; + + // Check contents - if no match - return false + try (InputStream oldIS = getJarFile().getInputStream(thisEntry); + InputStream newIS = f.getJarFile().getInputStream(e)) { + return !differs(oldIS, newIS); + } + } + + public String hasSameContent (JarFile2 file, JarEntry entry) throws IOException { + String thisName = null; + Long crcL = Long.valueOf(entry.getCrc()); + // check if this jar contains files with the passed in entry's crc + if (_crcToEntryMap.containsKey(crcL)) { + // get the Linked List with files with the crc + LinkedList ll = _crcToEntryMap.get(crcL); + // go through the list and check for content match + ListIterator li = ll.listIterator(0); + while (li.hasNext()) { + JarEntry thisEntry = li.next(); + // check for content match + try (InputStream oldIS = getJarFile().getInputStream(thisEntry); + InputStream newIS = file.getJarFile().getInputStream(entry)) { + if (!differs(oldIS, newIS)) { + thisName = thisEntry.getName(); + return thisName; + } + } + } + } + return thisName; + } + + private void index () throws IOException { + Enumeration entries = _jar.entries(); + + _nameToEntryMap = new HashMap<>(); + _crcToEntryMap = new HashMap<>(); + _entries = new ArrayList<>(); + if (_debug) { + System.out.println("indexing: " + _jar.getName()); + } + if (entries != null) { + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + long crc = entry.getCrc(); + Long crcL = Long.valueOf(crc); + if (_debug) { + System.out.println("\t" + entry.getName() + " CRC " + crc); + } + + _nameToEntryMap.put(entry.getName(), entry); + _entries.add(entry); + + // generate the CRC to entries map + if (_crcToEntryMap.containsKey(crcL)) { + // key exist, add the entry to the correcponding linked list + LinkedList ll = _crcToEntryMap.get(crcL); + ll.add(entry); + _crcToEntryMap.put(crcL, ll); + + } else { + // create a new entry in the hashmap for the new key + LinkedList ll = new LinkedList(); + ll.add(entry); + _crcToEntryMap.put(crcL, ll); + } + } + } + } + + @Override + public void close() throws IOException { + _jar.close(); + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java new file mode 100644 index 0000000..3b5db80 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java @@ -0,0 +1,24 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +/** + * Constants shared by {@link JarDiff} and {@link JarDiffPatcher}. + */ +public interface JarDiffCodes +{ + /** The name of the jardiff control file. */ + String INDEX_NAME = "META-INF/INDEX.JD"; + + /** The version header used in the control file. */ + String VERSION_HEADER = "version 1.0"; + + /** A jardiff command to remove an entry. */ + String REMOVE_COMMAND = "remove"; + + /** A jardiff command to move an entry. */ + String MOVE_COMMAND = "move"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java new file mode 100644 index 0000000..b5a0a17 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java @@ -0,0 +1,292 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +import com.threerings.getdown.util.ProgressObserver; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Applies a jardiff patch to a jar file. + */ +public class JarDiffPatcher implements JarDiffCodes +{ + /** + * Patches the specified jar file using the supplied patch file and writing + * the new jar file to the supplied target. + * + * @param jarPath the path to the original jar file. + * @param diffPath the path to the jardiff patch file. + * @param target the output stream to which we will write the patched jar. + * @param observer an optional observer to be notified of patching progress. + * + * @throws IOException if any problem occurs during patching. + */ + public void patchJar (String jarPath, String diffPath, File target, ProgressObserver observer) + throws IOException + { + File oldFile = new File(jarPath), diffFile = new File(diffPath); + + try (JarFile oldJar = new JarFile(oldFile); + JarFile jarDiff = new JarFile(diffFile); + JarOutputStream jos = new JarOutputStream(new FileOutputStream(target))) { + + Set ignoreSet = new HashSet<>(); + Map renameMap = new HashMap<>(); + determineNameMapping(jarDiff, ignoreSet, renameMap); + + // get all keys in renameMap + String[] keys = renameMap.keySet().toArray(new String[renameMap.size()]); + + // Files to implicit move + Set oldjarNames = new HashSet<>(); + Enumeration oldEntries = oldJar.entries(); + if (oldEntries != null) { + while (oldEntries.hasMoreElements()) { + oldjarNames.add(oldEntries.nextElement().getName()); + } + } + + // size depends on the three parameters below, which is basically the + // counter for each loop that do the actual writes to the output file + // since oldjarNames.size() changes in the first two loop below, we + // need to adjust the size accordingly also when oldjarNames.size() + // changes + double size = oldjarNames.size() + keys.length + jarDiff.size(); + double currentEntry = 0; + + // Handle all remove commands + oldjarNames.removeAll(ignoreSet); + size -= ignoreSet.size(); + + // Add content from JARDiff + Enumeration entries = jarDiff.entries(); + if (entries != null) { + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (!INDEX_NAME.equals(entry.getName())) { + updateObserver(observer, currentEntry, size); + currentEntry++; + writeEntry(jos, entry, jarDiff); + + // Remove entry from oldjarNames since no implicit move is + // needed + boolean wasInOld = oldjarNames.remove(entry.getName()); + + // Update progress counters. If it was in old, we do not + // need an implicit move, so adjust total size. + if (wasInOld) { + size--; + } + + } else { + // no write is done, decrement size + size--; + } + } + } + + // go through the renameMap and apply move for each entry + for (String newName : keys) { + // Apply move command + String oldName = renameMap.get(newName); + + // Get source JarEntry + JarEntry oldEntry = oldJar.getJarEntry(oldName); + if (oldEntry == null) { + String moveCmd = MOVE_COMMAND + oldName + " " + newName; + throw new IOException("error.badmove: " + moveCmd); + } + + // Create dest JarEntry + JarEntry newEntry = new JarEntry(newName); + newEntry.setTime(oldEntry.getTime()); + newEntry.setSize(oldEntry.getSize()); + newEntry.setCompressedSize(oldEntry.getCompressedSize()); + newEntry.setCrc(oldEntry.getCrc()); + newEntry.setMethod(oldEntry.getMethod()); + newEntry.setExtra(oldEntry.getExtra()); + newEntry.setComment(oldEntry.getComment()); + + updateObserver(observer, currentEntry, size); + currentEntry++; + + try (InputStream data = oldJar.getInputStream(oldEntry)) { + writeEntry(jos, newEntry, data); + } + + // Remove entry from oldjarNames since no implicit move is needed + boolean wasInOld = oldjarNames.remove(oldName); + + // Update progress counters. If it was in old, we do not need an + // implicit move, so adjust total size. + if (wasInOld) { + size--; + } + } + + // implicit move + Iterator iEntries = oldjarNames.iterator(); + if (iEntries != null) { + while (iEntries.hasNext()) { + String name = iEntries.next(); + JarEntry entry = oldJar.getJarEntry(name); + if (entry == null) { + // names originally retrieved from the JAR, so this should never happen + throw new AssertionError("JAR entry not found: " + name); + } + updateObserver(observer, currentEntry, size); + currentEntry++; + writeEntry(jos, entry, oldJar); + } + } + updateObserver(observer, currentEntry, size); + } + } + + protected void updateObserver (ProgressObserver observer, double currentSize, double size) + { + if (observer != null) { + observer.progress((int)(100*currentSize/size)); + } + } + + protected void determineNameMapping ( + JarFile jarDiff, Set ignoreSet, Map renameMap) + throws IOException + { + InputStream is = jarDiff.getInputStream(jarDiff.getEntry(INDEX_NAME)); + if (is == null) { + throw new IOException("error.noindex"); + } + + LineNumberReader indexReader = + new LineNumberReader(new InputStreamReader(is, UTF_8)); + String line = indexReader.readLine(); + if (line == null || !line.equals(VERSION_HEADER)) { + throw new IOException("jardiff.error.badheader: " + line); + } + + while ((line = indexReader.readLine()) != null) { + if (line.startsWith(REMOVE_COMMAND)) { + List sub = getSubpaths( + line.substring(REMOVE_COMMAND.length())); + + if (sub.size() != 1) { + throw new IOException("error.badremove: " + line); + } + ignoreSet.add(sub.get(0)); + + } else if (line.startsWith(MOVE_COMMAND)) { + List sub = getSubpaths( + line.substring(MOVE_COMMAND.length())); + if (sub.size() != 2) { + throw new IOException("error.badmove: " + line); + } + + // target of move should be the key + if (renameMap.put(sub.get(1), sub.get(0)) != null) { + // invalid move - should not move to same target twice + throw new IOException("error.badmove: " + line); + } + + } else if (line.length() > 0) { + throw new IOException("error.badcommand: " + line); + } + } + } + + protected List getSubpaths (String path) + { + int index = 0; + int length = path.length(); + ArrayList sub = new ArrayList<>(); + + while (index < length) { + while (index < length && Character.isWhitespace + (path.charAt(index))) { + index++; + } + if (index < length) { + int start = index; + int last = start; + String subString = null; + + while (index < length) { + char aChar = path.charAt(index); + if (aChar == '\\' && (index + 1) < length && + path.charAt(index + 1) == ' ') { + + if (subString == null) { + subString = path.substring(last, index); + } else { + subString += path.substring(last, index); + } + last = ++index; + } else if (Character.isWhitespace(aChar)) { + break; + } + index++; + } + if (last != index) { + if (subString == null) { + subString = path.substring(last, index); + } else { + subString += path.substring(last, index); + } + } + sub.add(subString); + } + } + return sub; + } + + protected void writeEntry (JarOutputStream jos, JarEntry entry, JarFile file) + throws IOException + { + try (InputStream data = file.getInputStream(entry)) { + writeEntry(jos, entry, data); + } + } + + protected void writeEntry (JarOutputStream jos, JarEntry entry, InputStream data) + throws IOException + { + jos.putNextEntry(new JarEntry(entry.getName())); + + // Read the entry + int size = data.read(newBytes); + while (size != -1) { + jos.write(newBytes, 0, size); + size = data.read(newBytes); + } + } + + protected static final int DEFAULT_READ_SIZE = 2048; + + protected static byte[] newBytes = new byte[DEFAULT_READ_SIZE]; + protected static byte[] oldBytes = new byte[DEFAULT_READ_SIZE]; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java new file mode 100644 index 0000000..4ead59b --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java @@ -0,0 +1,205 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import com.threerings.getdown.util.FileUtil; +import com.threerings.getdown.util.ProgressObserver; +import com.threerings.getdown.util.StreamUtil; + +import static com.threerings.getdown.Log.log; + +/** + * Applies a unified patch file to an application directory, providing + * percentage completion feedback along the way. Note: the + * patcher is not thread safe. Create a separate patcher instance for each + * patching action that is desired. + */ +public class Patcher +{ + /** A suffix appended to file names to indicate that a file should be newly created. */ + public static final String CREATE = ".create"; + + /** A suffix appended to file names to indicate that a file should be patched. */ + public static final String PATCH = ".patch"; + + /** A suffix appended to file names to indicate that a file should be deleted. */ + public static final String DELETE = ".delete"; + + /** + * Applies the specified patch file to the application living in the + * specified application directory. The supplied observer, if + * non-null, will be notified of progress along the way. + * + *

Note: this method runs on the calling thread, thus the + * caller may want to make use of a separate thread in conjunction + * with the patcher so that the user interface is not blocked for the + * duration of the patch. + */ + public void patch (File appdir, File patch, ProgressObserver obs) + throws IOException + { + // save this information for later + _obs = obs; + _plength = patch.length(); + + try (JarFile file = new JarFile(patch)) { + Enumeration entries = file.entries(); // old skool! + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String path = entry.getName(); + long elength = entry.getCompressedSize(); + + // depending on the suffix, we do The Right Thing (tm) + if (path.endsWith(CREATE)) { + path = strip(path, CREATE); + log.info("Creating " + path + "..."); + createFile(file, entry, new File(appdir, path)); + + } else if (path.endsWith(PATCH)) { + path = strip(path, PATCH); + log.info("Patching " + path + "..."); + patchFile(file, entry, appdir, path); + + } else if (path.endsWith(DELETE)) { + path = strip(path, DELETE); + log.info("Removing " + path + "..."); + File target = new File(appdir, path); + if (!FileUtil.deleteHarder(target)) { + log.warning("Failure deleting '" + target + "'."); + } + + } else { + log.warning("Skipping bogus patch file entry: " + path); + } + + // note that we've completed this entry + _complete += elength; + } + } + } + + protected String strip (String path, String suffix) + { + return path.substring(0, path.length() - suffix.length()); + } + + protected void createFile (JarFile file, ZipEntry entry, File target) + { + // create our copy buffer if necessary + if (_buffer == null) { + _buffer = new byte[COPY_BUFFER_SIZE]; + } + + // make sure the file's parent directory exists + File pdir = target.getParentFile(); + if (!pdir.exists() && !pdir.mkdirs()) { + log.warning("Failed to create parent for '" + target + "'."); + } + + try (InputStream in = file.getInputStream(entry); + FileOutputStream fout = new FileOutputStream(target)) { + + int total = 0, read; + while ((read = in.read(_buffer)) != -1) { + total += read; + fout.write(_buffer, 0, read); + updateProgress(total); + } + + } catch (IOException ioe) { + log.warning("Error creating '" + target + "': " + ioe); + } + } + + protected void patchFile (JarFile file, ZipEntry entry, + File appdir, String path) + { + File target = new File(appdir, path); + File patch = new File(appdir, entry.getName()); + File otarget = new File(appdir, path + ".old"); + JarDiffPatcher patcher = null; + + // make sure no stale old target is lying around to mess us up + FileUtil.deleteHarder(otarget); + + // pipe the contents of the patch into a file + try (InputStream in = file.getInputStream(entry); + FileOutputStream fout = new FileOutputStream(patch)) { + + StreamUtil.copy(in, fout); + StreamUtil.close(fout); + + // move the current version of the jar to .old + if (!FileUtil.renameTo(target, otarget)) { + log.warning("Failed to .oldify '" + target + "'."); + return; + } + + // we'll need this to pass progress along to our observer + final long elength = entry.getCompressedSize(); + ProgressObserver obs = new ProgressObserver() { + public void progress (int percent) { + updateProgress((int)(percent * elength / 100)); + } + }; + + // now apply the patch to create the new target file + patcher = new JarDiffPatcher(); + patcher.patchJar(otarget.getPath(), patch.getPath(), target, obs); + + } catch (IOException ioe) { + if (patcher == null) { + log.warning("Failed to write patch file '" + patch + "': " + ioe); + } else { + log.warning("Error patching '" + target + "': " + ioe); + } + + } finally { + // clean up our temporary files + FileUtil.deleteHarder(patch); + FileUtil.deleteHarder(otarget); + } + } + + protected void updateProgress (int progress) + { + if (_obs != null) { + _obs.progress((int)(100 * (_complete + progress) / _plength)); + } + } + + public static void main (String[] args) + { + if (args.length != 2) { + System.err.println("Usage: Patcher appdir patch_file"); + System.exit(-1); + } + + Patcher patcher = new Patcher(); + try { + patcher.patch(new File(args[0]), new File(args[1]), null); + } catch (IOException ioe) { + System.err.println("Error: " + ioe.getMessage()); + System.exit(-1); + } + } + + protected ProgressObserver _obs; + protected long _complete, _plength; + protected byte[] _buffer; + + protected static final int COPY_BUFFER_SIZE = 4096; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java new file mode 100644 index 0000000..2a5db79 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.threerings.getdown.util; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +/** + * Utilities for encoding and decoding the Base64 representation of + * binary data. See RFCs 2045 and 3548. + */ +public class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + + /** + * Flag to pass to {@code Base64OutputStream} to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + + /* package */ static abstract class Coder { + public byte[] output; + public int op; + + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process(byte[] input, int offset, int len, boolean finish); + + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param str the input String to decode, which is converted to + * bytes using ASCII + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(String str, int flags) { + return decode(str.getBytes(US_ASCII), flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int flags) { + return decode(input, 0, input.length, flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int offset, int len, int flags) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len*3/4]); + + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + + /* package */ static class Decoder extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int DECODE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int DECODE_WEBSAFE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** Non-data values in the DECODE arrays. */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + + final private int[] alphabet; + + public Decoder(int flags, byte[] output) { + this.output = output; + + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3/4 + 10; + } + + /** + * Decode another block of input data. + * + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process(byte[] input, int offset, int len, boolean finish) { + if (this.state == 6) return false; + + int p = offset; + len += offset; + + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p+4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p+1] & 0xff] << 12) | + (alphabet[input[p+2] & 0xff] << 6) | + (alphabet[input[p+3] & 0xff]))) >= 0) { + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + + // The fast path isn't available -- either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + + int d = alphabet[input[p++] & 0xff]; + + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op+1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + + this.state = state; + this.op = op; + return true; + } + } + + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int flags) { + return new String(encode(input, flags), US_ASCII); + } + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int offset, int len, int flags) { + return new String(encode(input, offset, len, flags), US_ASCII); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int flags) { + return encode(input, 0, input.length, flags); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int offset, int len, int flags) { + Encoder encoder = new Encoder(flags, null); + + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: break; + case 1: output_len += 2; break; + case 2: output_len += 3; break; + } + } + + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + + assert encoder.op == output_len; + + return encoder.output; + } + + /* package */ static class Encoder extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE_WEBSAFE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + + final private byte[] tail; + /* package */ int tailLen; + private int count; + + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + + public Encoder(int flags, byte[] output) { + this.output = output; + + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + + tail = new byte[2]; + tailLen = 0; + + count = do_newline ? LINE_GROUPS : -1; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8/5 + 10; + } + + public boolean process(byte[] input, int offset, int len, boolean finish) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + + int p = offset; + len += offset; + int v = -1; + + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + + switch (tailLen) { + case 0: + // There was no tail. + break; + + case 1: + if (p+2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + }; + break; + + case 2: + if (p+1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p+3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p+1] & 0xff) << 8) | + (input[p+2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op+1] = alphabet[(v >> 12) & 0x3f]; + output[op+2] = alphabet[(v >> 6) & 0x3f]; + output[op+3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + + if (p-tailLen == len-1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p-tailLen == len-2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + + if (p == len-1) { + tail[tailLen++] = input[p]; + } else if (p == len-2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p+1]; + } + } + + this.op = op; + this.count = count; + + return true; + } + } + + private Base64() { } // don't instantiate +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java new file mode 100644 index 0000000..047cead --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java @@ -0,0 +1,27 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +/** + * Utilities for handling ARGB colors. + */ +public class Color +{ + public final static int CLEAR = 0x00000000; + public final static int WHITE = 0xFFFFFFFF; + public final static int BLACK = 0xFF000000; + + public static float brightness (int argb) { + // TODO: we're ignoring alpha here... + int red = (argb >> 16) & 0xFF; + int green = (argb >> 8) & 0xFF; + int blue = (argb >> 0) & 0xFF; + int max = Math.max(Math.max(red, green), blue); + return ((float) max) / 255.0f; + } + + private Color () {} +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java new file mode 100644 index 0000000..4fc5e16 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java @@ -0,0 +1,378 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static com.threerings.getdown.Log.log; + +/** + * Handles parsing and runtime access for Getdown's config files (mainly {@code getdown.txt}). + * These files contain zero or more mappings for a particular string key. Config values can be + * fetched as single strings, lists of strings, or parsed into primitives or compound data types + * like colors and rectangles. + */ +public class Config +{ + /** Empty configuration. */ + public static final Config EMPTY = new Config(new HashMap()); + + /** Options that control the {@link #parsePairs} function. */ + public static class ParseOpts { + // these should be tweaked as desired by the caller + public boolean biasToKey = false; + public boolean strictComments = false; + + // these are filled in by parseConfig + public String osname = null; + public String osarch = null; + } + + /** + * Creates a parse configuration, filling in the platform filters (or not) depending on the + * value of {@code checkPlatform}. + */ + public static ParseOpts createOpts (boolean checkPlatform) { + ParseOpts opts = new ParseOpts(); + if (checkPlatform) { + opts.osname = StringUtil.deNull(System.getProperty("os.name")).toLowerCase(Locale.ROOT); + opts.osarch = StringUtil.deNull(System.getProperty("os.arch")).toLowerCase(Locale.ROOT); + } + return opts; + } + + /** + * Parses a configuration file containing key/value pairs. The file must be in the UTF-8 + * encoding. + * + * @param opts options that influence the parsing. See {@link #createOpts}. + * + * @return a list of String[] instances containing the key/value pairs in the + * order they were parsed from the file. + */ + public static List parsePairs (File source, ParseOpts opts) + throws IOException + { + // annoyingly FileReader does not allow encoding to be specified (uses platform default) + try (FileInputStream fis = new FileInputStream(source); + InputStreamReader input = new InputStreamReader(fis, StandardCharsets.UTF_8)) { + return parsePairs(input, opts); + } + } + + /** + * See {@link #parsePairs(File,ParseOpts)}. + */ + public static List parsePairs (Reader source, ParseOpts opts) throws IOException + { + List pairs = new ArrayList<>(); + for (String line : FileUtil.readLines(source)) { + // nix comments + int cidx = line.indexOf("#"); + if (opts.strictComments ? cidx == 0 : cidx != -1) { + line = line.substring(0, cidx); + } + + // trim whitespace and skip blank lines + line = line.trim(); + if (StringUtil.isBlank(line)) { + continue; + } + + // parse our key/value pair + String[] pair = new String[2]; + // if we're biasing toward key, put all the extra = in the key rather than the value + int eidx = opts.biasToKey ? line.lastIndexOf("=") : line.indexOf("="); + if (eidx != -1) { + pair[0] = line.substring(0, eidx).trim(); + pair[1] = line.substring(eidx+1).trim(); + } else { + pair[0] = line; + pair[1] = ""; + } + + // if the pair has an os qualifier, we need to process it + if (pair[1].startsWith("[")) { + int qidx = pair[1].indexOf("]"); + if (qidx == -1) { + log.warning("Bogus platform specifier", "key", pair[0], "value", pair[1]); + continue; // omit the pair entirely + } + // if we're checking qualifiers and the os doesn't match this qualifier, skip it + String quals = pair[1].substring(1, qidx); + if (opts.osname != null && !checkQualifiers(quals, opts.osname, opts.osarch)) { + log.debug("Skipping", "quals", quals, + "osname", opts.osname, "osarch", opts.osarch, + "key", pair[0], "value", pair[1]); + continue; + } + // otherwise filter out the qualifier text + pair[1] = pair[1].substring(qidx+1).trim(); + } + + pairs.add(pair); + } + + return pairs; + } + + /** + * Takes a comma-separated String of four integers and returns a rectangle using those ints as + * the its x, y, width, and height. + */ + public static Rectangle parseRect (String name, String value) + { + if (!StringUtil.isBlank(value)) { + int[] v = StringUtil.parseIntArray(value); + if (v != null && v.length == 4) { + return new Rectangle(v[0], v[1], v[2], v[3]); + } + log.warning("Ignoring invalid rect '" + name + "' config '" + value + "'."); + } + return null; + } + + /** + * Parses the given hex color value (e.g. FFCC99) and returns an {@code Integer} with that + * value. If the given value is null or not a valid hexadecimal number, this will return null. + */ + public static Integer parseColor (String hexValue) + { + if (!StringUtil.isBlank(hexValue)) { + try { + // if no alpha channel is specified, use 255 (full alpha) + int alpha = hexValue.length() > 6 ? 0 : 0xFF000000; + return Integer.parseInt(hexValue, 16) | alpha; + } catch (NumberFormatException e) { + log.warning("Ignoring invalid color", "hexValue", hexValue, "exception", e); + } + } + return null; + } + + /** + * Parses a configuration file containing key/value pairs. The file must be in the UTF-8 + * encoding. + * + * @return a map from keys to values, where a value will be an array of strings if more than + * one key/value pair in the config file was associated with the same key. + */ + public static Config parseConfig (File source, ParseOpts opts) + throws IOException + { + Map data = new HashMap<>(); + + // I thought that we could use HashMap and put new String[] {pair[1]} for + // the null case, but it mysteriously dies on launch, so leaving it as HashMap for now + for (String[] pair : parsePairs(source, opts)) { + Object value = data.get(pair[0]); + if (value == null) { + data.put(pair[0], pair[1]); + } else if (value instanceof String) { + data.put(pair[0], new String[] { (String)value, pair[1] }); + } else if (value instanceof String[]) { + String[] values = (String[])value; + String[] nvalues = new String[values.length+1]; + System.arraycopy(values, 0, nvalues, 0, values.length); + nvalues[values.length] = pair[1]; + data.put(pair[0], nvalues); + } + } + + // special magic for the getdown.txt config: if the parsed data contains 'strict_comments = + // true' then we reparse the file with strict comments (i.e. # is only assumed to start a + // comment in column 0) + if (!opts.strictComments && Boolean.parseBoolean((String)data.get("strict_comments"))) { + opts.strictComments = true; + return parseConfig(source, opts); + } + + return new Config(data); + } + + public Config (Map data) { + _data = data; + } + + /** + * Returns whether {@code name} has a value in this config. + */ + public boolean hasValue (String name) { + return _data.containsKey(name); + } + + /** + * Returns the raw-value for {@code name}. This may be a {@code String}, {@code String[]}, or + * {@code null}. + */ + public Object getRaw (String name) { + return _data.get(name); + } + + /** + * Returns the specified config value as a string, or {@code null}. + */ + public String getString (String name) { + return (String)_data.get(name); + } + + /** + * Returns the specified config value as a string, or {@code def}. + */ + public String getString (String name, String def) { + String value = (String)_data.get(name); + return value == null ? def : value; + } + + /** + * Returns the specified config value as a boolean. + */ + public boolean getBoolean (String name) { + return Boolean.parseBoolean(getString(name)); + } + + /** + * Massages a single string into an array and leaves existing array values as is. Simplifies + * access to parameters that are expected to be arrays. + */ + public String[] getMultiValue (String name) + { + Object value = _data.get(name); + if (value instanceof String) { + return new String[] { (String)value }; + } else { + return (String[])value; + } + } + + /** Used to parse rectangle specifications from the config file. */ + public Rectangle getRect (String name, Rectangle def) + { + String value = getString(name); + Rectangle rect = parseRect(name, value); + return (rect == null) ? def : rect; + } + + /** + * Parses and returns the config value for {@code name} as an int. If no value is provided, + * {@code def} is returned. If the value is invalid, a warning is logged and {@code def} is + * returned. + */ + public int getInt (String name, int def) { + String value = getString(name); + try { + return value == null ? def : Integer.parseInt(value); + } catch (Exception e) { + log.warning("Ignoring invalid int '" + name + "' config '" + value + "',"); + return def; + } + } + + /** + * Parses and returns the config value for {@code name} as a long. If no value is provided, + * {@code def} is returned. If the value is invalid, a warning is logged and {@code def} is + * returned. + */ + public long getLong (String name, long def) { + String value = getString(name); + try { + return value == null ? def : Long.parseLong(value); + } catch (Exception e) { + log.warning("Ignoring invalid long '" + name + "' config '" + value + "',"); + return def; + } + } + + /** Used to parse color specifications from the config file. */ + public int getColor (String name, int def) + { + String value = getString(name); + Integer color = parseColor(value); + return (color == null) ? def : color; + } + + /** Parses a list of strings from the config file. */ + public String[] getList (String name) + { + String value = getString(name); + return (value == null) ? new String[0] : StringUtil.parseStringArray(value); + } + + /** + * Parses a URL from the config file, checking first for a localized version. + */ + public String getUrl (String name, String def) + { + String value = getString(name + "." + Locale.getDefault().getLanguage()); + if (StringUtil.isBlank(value)) { + value = getString(name); + } + if (StringUtil.isBlank(value)) { + value = def; + } + if (!StringUtil.isBlank(value)) { + try { + HostWhitelist.verify(new URL(value)); + } catch (MalformedURLException e) { + log.warning("Invalid URL.", "url", value, e); + value = null; + } + } + return value; + } + + /** + * A helper function for {@link #parsePairs(Reader,ParseOpts)}. Qualifiers have the following + * form: + *

+     * id = os[-arch]
+     * ids = id | id,ids
+     * quals = !id | ids
+     * 
+ * Examples: [linux-amd64,linux-x86_64], [windows], [mac os x], [!windows]. Negative qualifiers + * must appear alone, they cannot be used with other qualifiers (positive or negative). + */ + protected static boolean checkQualifiers (String quals, String osname, String osarch) + { + if (quals.startsWith("!")) { + if (quals.indexOf(",") != -1) { // sanity check + log.warning("Multiple qualifiers cannot be used when one of the qualifiers " + + "is negative", "quals", quals); + return false; + } + return !checkQualifier(quals.substring(1), osname, osarch); + } + for (String qual : quals.split(",")) { + if (checkQualifier(qual, osname, osarch)) { + return true; // if we have a positive match, we can immediately return true + } + } + return false; // we had no positive matches, so return false + } + + /** A helper function for {@link #checkQualifiers}. */ + protected static boolean checkQualifier (String qual, String osname, String osarch) + { + String[] bits = qual.trim().toLowerCase(Locale.ROOT).split("-"); + String os = bits[0], arch = (bits.length > 1) ? bits[1] : ""; + return (osname.indexOf(os) != -1) && (osarch.indexOf(arch) != -1); + } + + private final Map _data; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java new file mode 100644 index 0000000..21b0569 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java @@ -0,0 +1,73 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; + +import com.threerings.getdown.data.SysProps; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class ConnectionUtil +{ + /** + * Opens a connection to a URL, setting the authentication header if user info is present. + * @param proxy the proxy via which to perform HTTP connections. + * @param url the URL to which to open a connection. + * @param connectTimeout if {@code > 0} then a timeout, in seconds, to use when opening the + * connection. If {@code 0} is supplied, the connection timeout specified via system properties + * will be used instead. + * @param readTimeout if {@code > 0} then a timeout, in seconds, to use while reading data from + * the connection. If {@code 0} is supplied, the read timeout specified via system properties + * will be used instead. + */ + public static URLConnection open (Proxy proxy, URL url, int connectTimeout, int readTimeout) + throws IOException + { + URLConnection conn = url.openConnection(proxy); + + // configure a connect timeout, if requested + int ctimeout = connectTimeout > 0 ? connectTimeout : SysProps.connectTimeout(); + if (ctimeout > 0) { + conn.setConnectTimeout(ctimeout * 1000); + } + + // configure a read timeout, if requested + int rtimeout = readTimeout > 0 ? readTimeout : SysProps.readTimeout(); + if (rtimeout > 0) { + conn.setReadTimeout(rtimeout * 1000); + } + + // If URL has a username:password@ before hostname, use HTTP basic auth + String userInfo = url.getUserInfo(); + if (userInfo != null) { + // Remove any percent-encoding in the username/password + userInfo = URLDecoder.decode(userInfo, "UTF-8"); + // Now base64 encode the auth info and make it a single line + String encoded = Base64.encodeToString(userInfo.getBytes(UTF_8), Base64.DEFAULT). + replaceAll("\\n","").replaceAll("\\r", ""); + conn.setRequestProperty("Authorization", "Basic " + encoded); + } + + return conn; + } + + /** + * Opens a connection to a http or https URL, setting the authentication header if user info is + * present. Throws a class cast exception if the connection returned is not the right type. See + * {@link #open} for parameter documentation. + */ + public static HttpURLConnection openHttp ( + Proxy proxy, URL url, int connectTimeout, int readTimeout) throws IOException + { + return (HttpURLConnection)open(proxy, url, connectTimeout, readTimeout); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java new file mode 100644 index 0000000..ec9d887 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java @@ -0,0 +1,239 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.io.*; +import java.util.*; +import java.util.jar.*; +import java.util.zip.GZIPInputStream; + +import com.threerings.getdown.Log; +import static com.threerings.getdown.Log.log; + +/** + * File related utilities. + */ +public class FileUtil +{ + /** + * Gets the specified source file to the specified destination file by hook or crook. Windows + * has all sorts of problems which we work around in this method. + * + * @return true if we managed to get the job done, false otherwise. + */ + public static boolean renameTo (File source, File dest) + { + // if we're on a civilized operating system we may be able to simple rename it + if (source.renameTo(dest)) { + return true; + } + + // fall back to trying to rename the old file out of the way, rename the new file into + // place and then delete the old file + if (dest.exists()) { + File temp = new File(dest.getPath() + "_old"); + if (temp.exists() && !deleteHarder(temp)) { + log.warning("Failed to delete old intermediate file " + temp + "."); + // the subsequent code will probably fail + } + if (dest.renameTo(temp) && source.renameTo(dest)) { + if (!deleteHarder(temp)) { + log.warning("Failed to delete intermediate file " + temp + "."); + } + return true; + } + } + + // as a last resort, try copying the old data over the new + try { + copy(source, dest); + } catch (IOException ioe) { + log.warning("Failed to copy " + source + " to " + dest + ": " + ioe); + return false; + } + + if (!deleteHarder(source)) { + log.warning("Failed to delete " + source + " after brute force copy to " + dest + "."); + } + return true; + } + + /** + * "Tries harder" to delete {@code file} than just calling {@code delete} on it. Presently this + * just means "try a second time if the first time fails, and if that fails then try to delete + * when the virtual machine terminates." On Windows Vista, sometimes deletes fail but then + * succeed if you just try again. Given that delete failure is a rare occurrence, we can + * implement this hacky workaround without any negative consequences for normal behavior. + */ + public static boolean deleteHarder (File file) { + // if at first you don't succeed... try, try again + boolean deleted = (file.delete() || file.delete()); + if (!deleted) { + file.deleteOnExit(); + } + return deleted; + } + + /** + * Force deletes {@code file} and all of its children recursively using {@link #deleteHarder}. + * Note that some children may still be deleted even if {@code false} is returned. Also, since + * {@link #deleteHarder} is used, the {@code file} could be deleted once the jvm exits even if + * {@code false} is returned. + * + * @param file file to delete. + * @return true iff {@code file} was successfully deleted. + */ + public static boolean deleteDirHarder (File file) { + if (file.isDirectory()) { + for (File child : file.listFiles()) { + deleteDirHarder(child); + } + } + return deleteHarder(file); + } + + /** + * Reads the contents of the supplied input stream into a list of lines. Closes the reader on + * successful or failed completion. + */ + public static List readLines (Reader in) + throws IOException + { + List lines = new ArrayList<>(); + try (BufferedReader bin = new BufferedReader(in)) { + for (String line = null; (line = bin.readLine()) != null; lines.add(line)) {} + } + return lines; + } + + /** + * Unpacks the specified jar file into the specified target directory. + * @param cleanExistingDirs if true, all files in all directories contained in {@code jar} will + * be deleted prior to unpacking the jar. + */ + public static void unpackJar (JarFile jar, File target, boolean cleanExistingDirs) + throws IOException + { + if (cleanExistingDirs) { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry)entries.nextElement(); + if (entry.isDirectory()) { + File efile = new File(target, entry.getName()); + if (efile.exists()) { + for (File f : efile.listFiles()) { + if (!f.isDirectory()) + f.delete(); + } + } + } + } + } + + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry)entries.nextElement(); + File efile = new File(target, entry.getName()); + + // if we're unpacking a normal jar file, it will have special path + // entries that allow us to create our directories first + if (entry.isDirectory()) { + if (!efile.exists() && !efile.mkdir()) { + log.warning("Failed to create jar entry path", "jar", jar, "entry", entry); + } + continue; + } + + // but some do not, so we want to ensure that our directories exist + // prior to getting down and funky + File parent = new File(efile.getParent()); + if (!parent.exists() && !parent.mkdirs()) { + log.warning("Failed to create jar entry parent", "jar", jar, "parent", parent); + continue; + } + + try (BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(efile)); + InputStream jin = jar.getInputStream(entry)) { + StreamUtil.copy(jin, fout); + } catch (Exception e) { + throw new IOException( + Log.format("Failure unpacking", "jar", jar, "entry", efile), e); + } + } + } + + /** + * Unpacks a pack200 packed jar file from {@code packedJar} into {@code target}. If {@code + * packedJar} has a {@code .gz} extension, it will be gunzipped first. + */ + public static void unpackPacked200Jar (File packedJar, File target) throws IOException + { + try (InputStream packJarIn = new FileInputStream(packedJar); + JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(target))) { + boolean gz = (packedJar.getName().endsWith(".gz") || + packedJar.getName().endsWith(".gz_new")); + try (InputStream packJarIn2 = (gz ? new GZIPInputStream(packJarIn) : packJarIn)) { + Pack200.Unpacker unpacker = Pack200.newUnpacker(); + unpacker.unpack(packJarIn2, jarOut); + } + } + } + + /** + * Copies the given {@code source} file to the given {@code target}. + */ + public static void copy (File source, File target) throws IOException { + try (FileInputStream in = new FileInputStream(source); + FileOutputStream out = new FileOutputStream(target)) { + StreamUtil.copy(in, out); + } + } + + /** + * Marks {@code file} as executable, if it exists. Catches and logs any errors that occur. + */ + public static void makeExecutable (File file) { + try { + if (file.exists()) { + if (!file.setExecutable(true, false)) { + log.warning("Failed to mark as executable", "file", file); + } + } + } catch (Exception e) { + log.warning("Failed to mark as executable", "file", file, "error", e); + } + } + + /** + * Used by {@link #walkTree}. + */ + public interface Visitor + { + void visit (File file); + } + + /** + * Walks all files in {@code root}, calling {@code visitor} on each file in the tree. + */ + public static void walkTree (File root, Visitor visitor) + { + File[] children = root.listFiles(); + if (children == null) return; + Deque stack = new ArrayDeque<>(Arrays.asList(children)); + while (!stack.isEmpty()) { + File currentFile = stack.pop(); + if (currentFile.exists()) { + visitor.visit(currentFile); + File[] currentChildren = currentFile.listFiles(); + if (currentChildren != null) { + for (File file : currentChildren) { + stack.push(file); + } + } + } + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java new file mode 100644 index 0000000..c05992a --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java @@ -0,0 +1,54 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +import com.threerings.getdown.data.Build; + +/** + * Optional support for compiling a URL host whitelist into the Getdown JAR. + * Useful if you're on the paranoid end of the security spectrum. + * + * @see Build#hostWhitelist() + */ +public final class HostWhitelist +{ + /** + * Verifies that the specified URL should be accessible, per the built-in host whitelist. + * See {@link Build#hostWhitelist()} and {@link #verify(List,URL)}. + */ + public static URL verify (URL url) throws MalformedURLException + { + return verify(Build.hostWhitelist(), url); + } + + /** + * Verifies that the specified URL should be accessible, per the supplied host whitelist. + * If the URL should not be accessible, this method throws a {@link MalformedURLException}. + * If the URL should be accessible, this method simply returns the {@link URL} passed in. + */ + public static URL verify (List hosts, URL url) throws MalformedURLException + { + if (url == null || hosts.isEmpty()) { + // either there is no URL to validate or no whitelist was configured + return url; + } + + String urlHost = url.getHost(); + for (String host : hosts) { + String regex = host.replace(".", "\\.").replace("*", ".*"); + if (urlHost.matches(regex)) { + return url; + } + } + + throw new MalformedURLException( + "The host for the specified URL (" + url + ") is not in the host whitelist: " + hosts); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java new file mode 100644 index 0000000..829e38f --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java @@ -0,0 +1,251 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Locale; + +import static com.threerings.getdown.Log.log; + +/** + * Useful routines for launching Java applications from within other Java + * applications. + */ +public class LaunchUtil +{ + /** The directory into which a local VM installation should be unpacked. */ + public static final String LOCAL_JAVA_DIR = "java_vm"; + + /** + * Writes a version.txt file into the specified application directory and + * attempts to relaunch Getdown in that directory which will cause it to upgrade to the newly + * specified version and relaunch the application. + * + * @param appdir the directory in which the application is installed. + * @param getdownJarName the name of the getdown jar file in the application directory. This is + * probably getdown-pro.jar or getdown-retro-pro.jar if you are using + * the results of the standard build. + * @param newVersion the new version to which Getdown will update when it is executed. + * + * @return true if the relaunch succeeded, false if we were unable to relaunch due to being on + * Windows 9x where we cannot launch subprocesses without waiting around for them to exit, + * reading their stdout and stderr all the while. If true is returned, the application may exit + * after making this call as it will be upgraded and restarted. If false is returned, the + * application should tell the user that they must restart the application manually. + * + * @exception IOException thrown if we were unable to create the version.txt file + * in the supplied application directory. If the version.txt file cannot be created, restarting + * Getdown will not cause the application to be upgraded, so the application will have to + * resort to telling the user that it is in a bad way. + */ + public static boolean updateVersionAndRelaunch ( + File appdir, String getdownJarName, String newVersion) + throws IOException + { + // create the file that instructs Getdown to upgrade + File vfile = new File(appdir, "version.txt"); + try (PrintStream ps = new PrintStream(new FileOutputStream(vfile))) { + ps.println(newVersion); + } + + // make sure that we can find our getdown.jar file and can safely launch children + File pro = new File(appdir, getdownJarName); + if (mustMonitorChildren() || !pro.exists()) { + return false; + } + + // do the deed + String[] args = new String[] { + getJVMPath(appdir), "-jar", pro.toString(), appdir.getPath() + }; + log.info("Running " + StringUtil.join(args, "\n ")); + try { + Runtime.getRuntime().exec(args, null); + return true; + } catch (IOException ioe) { + log.warning("Failed to run getdown", ioe); + return false; + } + } + + /** + * Reconstructs the path to the JVM used to launch this process. + */ + public static String getJVMPath (File appdir) + { + return getJVMPath(appdir, false); + } + + /** + * Reconstructs the path to the JVM used to launch this process. + * + * @param windebug if true we will use java.exe instead of javaw.exe on Windows. + */ + public static String getJVMPath (File appdir, boolean windebug) + { + // first look in our application directory for an installed VM + String vmpath = checkJVMPath(new File(appdir, LOCAL_JAVA_DIR).getAbsolutePath(), windebug); + if (vmpath == null && isMacOS()) { + vmpath = checkJVMPath(new File(appdir, LOCAL_JAVA_DIR + "/Contents/Home").getAbsolutePath(), windebug); + } + + // then fall back to the VM in which we're already running + if (vmpath == null) { + vmpath = checkJVMPath(System.getProperty("java.home"), windebug); + } + + // then throw up our hands and hope for the best + if (vmpath == null) { + log.warning("Unable to find java [appdir=" + appdir + + ", java.home=" + System.getProperty("java.home") + "]!"); + vmpath = "java"; + } + + // Oddly, the Mac OS X specific java flag -Xdock:name will only work if java is launched + // from /usr/bin/java, and not if launched by directly referring to /bin/java, + // even though the former is a symlink to the latter! To work around this, see if the + // desired jvm is in fact pointed to by /usr/bin/java and, if so, use that instead. + if (isMacOS()) { + try { + File localVM = new File("/usr/bin/java").getCanonicalFile(); + if (localVM.equals(new File(vmpath).getCanonicalFile())) { + vmpath = "/usr/bin/java"; + } + } catch (IOException ioe) { + log.warning("Failed to check Mac OS canonical VM path.", ioe); + } + } + + return vmpath; + } + + /** + * Upgrades Getdown by moving an installation managed copy of the Getdown jar file over the + * non-managed copy (which would be used to run Getdown itself). + * + *

If the upgrade fails for a variety of reasons, warnings are logged but no other actions + * are taken. There's not much else one can do other than try again next time around. + */ + public static void upgradeGetdown (File oldgd, File curgd, File newgd) + { + // we assume getdown's jar file size changes with every upgrade, this is not guaranteed, + // but in reality it will, and it allows us to avoid pointlessly upgrading getdown every + // time the client is updated which is unnecessarily flirting with danger + if (!newgd.exists() || newgd.length() == curgd.length()) { + return; + } + + log.info("Updating Getdown with " + newgd + "..."); + + // clear out any old getdown + if (oldgd.exists()) { + FileUtil.deleteHarder(oldgd); + } + + // now try updating using renames + if (!curgd.exists() || curgd.renameTo(oldgd)) { + if (newgd.renameTo(curgd)) { + FileUtil.deleteHarder(oldgd); // yay! + try { + // copy the moved file back to getdown-dop-new.jar so that we don't end up + // downloading another copy next time + FileUtil.copy(curgd, newgd); + } catch (IOException e) { + log.warning("Error copying updated Getdown back: " + e); + } + return; + } + + log.warning("Unable to renameTo(" + oldgd + ")."); + // try to unfuck ourselves + if (!oldgd.renameTo(curgd)) { + log.warning("Oh God, why dost thee scorn me so."); + } + } + + // that didn't work, let's try copying it + log.info("Attempting to upgrade by copying over " + curgd + "..."); + try { + FileUtil.copy(newgd, curgd); + } catch (IOException ioe) { + log.warning("Mayday! Brute force copy method also failed.", ioe); + } + } + + /** + * Returns true if, on this operating system, we have to stick around and read the stderr from + * our children processes to prevent them from filling their output buffers and hanging. + */ + public static boolean mustMonitorChildren () + { + String osname = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); + return (osname.indexOf("windows 98") != -1 || osname.indexOf("windows me") != -1); + } + + /** + * Returns true if we're running in a JVM that identifies its operating system as Windows. + */ + public static final boolean isWindows () { return _isWindows; } + + /** + * Returns true if we're running in a JVM that identifies its operating system as MacOS. + */ + public static final boolean isMacOS () { return _isMacOS; } + + /** + * Returns true if we're running in a JVM that identifies its operating system as Linux. + */ + public static final boolean isLinux () { return _isLinux; } + + /** + * Checks whether a Java Virtual Machine can be located in the supplied path. + */ + protected static String checkJVMPath (String vmhome, boolean windebug) + { + String vmbase = vmhome + File.separator + "bin" + File.separator; + String vmpath = vmbase + "java"; + if (new File(vmpath).exists()) { + return vmpath; + } + + if (!windebug) { + vmpath = vmbase + "javaw.exe"; + if (new File(vmpath).exists()) { + return vmpath; + } + } + + vmpath = vmbase + "java.exe"; + if (new File(vmpath).exists()) { + return vmpath; + } + + return null; + } + + /** Flag indicating that we're on Windows; initialized when this class is first loaded. */ + protected static boolean _isWindows; + /** Flag indicating that we're on MacOS; initialized when this class is first loaded. */ + protected static boolean _isMacOS; + /** Flag indicating that we're on Linux; initialized when this class is first loaded. */ + protected static boolean _isLinux; + + static { + try { + String osname = System.getProperty("os.name"); + osname = (osname == null) ? "" : osname; + _isWindows = (osname.indexOf("Windows") != -1); + _isMacOS = (osname.indexOf("Mac OS") != -1 || + osname.indexOf("MacOS") != -1); + _isLinux = (osname.indexOf("Linux") != -1); + } catch (Exception e) { + // can't grab system properties; we'll just pretend we're not on any of these OSes + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java new file mode 100644 index 0000000..28dbdcf --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java @@ -0,0 +1,144 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +public class MessageUtil { + + /** + * Returns whether or not the provided string is tainted. See {@link #taint}. Null strings + * are considered untainted. + */ + public static boolean isTainted (String text) + { + return text != null && text.startsWith(TAINT_CHAR); + } + + /** + * Call this to "taint" any string that has been entered by an entity outside the application + * so that the translation code knows not to attempt to translate this string when doing + * recursive translations. + */ + public static String taint (Object text) + { + return TAINT_CHAR + text; + } + + /** + * Removes the tainting character added to a string by {@link #taint}. If the provided string + * is not tainted, this silently returns the originally provided string. + */ + public static String untaint (String text) + { + return isTainted(text) ? text.substring(TAINT_CHAR.length()) : text; + } + + /** + * Composes a message key with an array of arguments. The message can subsequently be + * decomposed and translated without prior knowledge of how many arguments were provided. + */ + public static String compose (String key, Object... args) + { + StringBuilder buf = new StringBuilder(); + buf.append(key); + buf.append('|'); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + buf.append('|'); + } + // escape the string while adding to the buffer + String arg = (args[i] == null) ? "" : String.valueOf(args[i]); + int alength = arg.length(); + for (int p = 0; p < alength; p++) { + char ch = arg.charAt(p); + if (ch == '|') { + buf.append("\\!"); + } else if (ch == '\\') { + buf.append("\\\\"); + } else { + buf.append(ch); + } + } + } + return buf.toString(); + } + + /** + * Compose a message with String args. This is just a convenience so callers do not have to + * cast their String[] to an Object[]. + */ + public static String compose (String key, String... args) + { + return compose(key, (Object[]) args); + } + + /** + * A convenience method for calling {@link #compose(String,Object[])} with an array of + * arguments that will be automatically tainted (see {@link #taint}). + */ + public static String tcompose (String key, Object... args) + { + int acount = args.length; + String[] targs = new String[acount]; + for (int ii = 0; ii < acount; ii++) { + targs[ii] = taint(args[ii]); + } + return compose(key, (Object[]) targs); + } + + /** + * A convenience method for calling {@link #compose(String,String[])} with an array of argument + * that will be automatically tainted. + */ + public static String tcompose (String key, String... args) + { + for (int ii = 0, nn = args.length; ii < nn; ii++) { + args[ii] = taint(args[ii]); + } + return compose(key, args); + } + + /** + * Used to escape single quotes so that they are not interpreted by MessageFormat. + * As we assume all single quotes are to be escaped, we cannot use the characters + * { and } in our translation strings, but this is a small price to + * pay to have to differentiate between messages that will and won't eventually be parsed by a + * MessageFormat instance. + */ + public static String escape (String message) + { + return message.replace("'", "''"); + } + + /** + * Unescapes characters that are escaped in a call to compose. + */ + public static String unescape (String value) + { + int bsidx = value.indexOf('\\'); + if (bsidx == -1) { + return value; + } + + StringBuilder buf = new StringBuilder(); + int vlength = value.length(); + for (int ii = 0; ii < vlength; ii++) { + char ch = value.charAt(ii); + if (ch != '\\' || ii == vlength-1) { + buf.append(ch); + } else { + // look at the next character + ch = value.charAt(++ii); + buf.append((ch == '!') ? '|' : ch); + } + } + + return buf.toString(); + } + + /** Text prefixed by this character will be considered tainted when doing recursive + * translations and won't be translated. */ + protected static final String TAINT_CHAR = "~"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java new file mode 100644 index 0000000..d74b011 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java @@ -0,0 +1,50 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +/** + * Accumulates the progress from a number of (potentially parallel) elements into a single smoothly + * progressing progress. + */ +public class ProgressAggregator +{ + public ProgressAggregator (ProgressObserver target, long[] sizes) { + _target = target; + _sizes = sizes; + _progress = new int[sizes.length]; + } + + public ProgressObserver startElement (final int index) { + return new ProgressObserver() { + public void progress (int percent) { + _progress[index] = percent; + updateAggProgress(); + } + }; + } + + protected void updateAggProgress () { + long totalSize = 0L, currentSize = 0L; + synchronized (this) { + for (int ii = 0, ll = _sizes.length; ii < ll; ii++) { + long size = _sizes[ii]; + totalSize += size; + currentSize += (int)((size * _progress[ii])/100.0); + } + } + _target.progress((int)(100.0*currentSize / totalSize)); + } + + protected static long sum (long[] sizes) { + long totalSize = 0L; + for (long size : sizes) totalSize += size; + return totalSize; + } + + protected ProgressObserver _target; + protected long[] _sizes; + protected int[] _progress; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java new file mode 100644 index 0000000..ad4c560 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java @@ -0,0 +1,18 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +/** + * Used to communicate progress. + */ +public interface ProgressObserver +{ + /** + * Informs the observer that we have completed the specified + * percentage of the process. + */ + public void progress (int percent); +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java new file mode 100644 index 0000000..3671d7d --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java @@ -0,0 +1,40 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +/** + * An immutable rectangle. + */ +public class Rectangle +{ + public final int x; + public final int y; + public final int width; + public final int height; + + public Rectangle (int x, int y, int width, int height) + { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public Rectangle union (Rectangle other) { + int x1 = Math.min(x, other.x); + int x2 = Math.max(x + width, other.x + other.width); + int y1 = Math.min(y, other.y); + int y2 = Math.max(y + height, other.y + other.height); + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } + + /** {@inheritDoc} */ + public String toString () + { + return getClass().getName() + "[x=" + x + ", y=" + y + + ", width=" + width + ", height=" + height + "]"; + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java new file mode 100644 index 0000000..373cfff --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java @@ -0,0 +1,96 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.Charset; + +import static com.threerings.getdown.Log.log; + +public class StreamUtil { + /** + * Convenient close for a stream. Use in a finally clause and love life. + */ + public static void close (InputStream in) + { + if (in != null) { + try { + in.close(); + } catch (IOException ioe) { + log.warning("Error closing input stream", "stream", in, "cause", ioe); + } + } + } + + /** + * Convenient close for a stream. Use in a finally clause and love life. + */ + public static void close (OutputStream out) + { + if (out != null) { + try { + out.close(); + } catch (IOException ioe) { + log.warning("Error closing output stream", "stream", out, "cause", ioe); + } + } + } + + /** + * Convenient close for a Reader. Use in a finally clause and love life. + */ + public static void close (Reader in) + { + if (in != null) { + try { + in.close(); + } catch (IOException ioe) { + log.warning("Error closing reader", "reader", in, "cause", ioe); + } + } + } + + /** + * Convenient close for a Writer. Use in a finally clause and love life. + */ + public static void close (Writer out) + { + if (out != null) { + try { + out.close(); + } catch (IOException ioe) { + log.warning("Error closing writer", "writer", out, "cause", ioe); + } + } + } + + /** + * Copies the contents of the supplied input stream to the supplied output stream. + */ + public static T copy (InputStream in, T out) + throws IOException + { + byte[] buffer = new byte[4096]; + for (int read = 0; (read = in.read(buffer)) > 0; ) { + out.write(buffer, 0, read); + } + return out; + } + + /** + * Reads the contents of the supplied stream into a byte array. + */ + public static byte[] toByteArray (InputStream stream) + throws IOException + { + return copy(stream, new ByteArrayOutputStream()).toByteArray(); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java new file mode 100644 index 0000000..03d3c9c --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java @@ -0,0 +1,206 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.util.StringTokenizer; + +public class StringUtil { + + /** + * @return true if the specified string could be a valid URL (contains no illegal characters) + */ + public static boolean couldBeValidUrl (String url) + { + return url.matches("[A-Za-z0-9\\-\\._~:/\\?#\\[\\]@!$&'\\(\\)\\*\\+,;=%]+"); + } + + /** + * @return true if the string is null or consists only of whitespace, false otherwise. + */ + public static boolean isBlank (String value) + { + for (int ii = 0, ll = (value == null) ? 0 : value.length(); ii < ll; ii++) { + if (!Character.isWhitespace(value.charAt(ii))) { + return false; + } + } + return true; + } + + /** + * Parses an array of integers from it's string representation. The array should be represented + * as a bare list of numbers separated by commas, for example: + * + *

25, 17, 21, 99
+ * + * Any inability to parse the int array will result in the function returning null. + */ + public static int[] parseIntArray (String source) + { + StringTokenizer tok = new StringTokenizer(source, ","); + int[] vals = new int[tok.countTokens()]; + for (int i = 0; tok.hasMoreTokens(); i++) { + try { + // trim the whitespace from the token + vals[i] = Integer.parseInt(tok.nextToken().trim()); + } catch (NumberFormatException nfe) { + return null; + } + } + return vals; + } + + /** + * Parses an array of strings from a single string. The array should be represented as a bare + * list of strings separated by commas, for example: + * + *
mary, had, a, little, lamb, and, an, escaped, comma,,
+ * + * If a comma is desired in one of the strings, it should be escaped by putting two commas in a + * row. Any inability to parse the string array will result in the function returning null. + */ + public static String[] parseStringArray (String source) + { + return parseStringArray(source, false); + } + + /** + * Like {@link #parseStringArray(String)} but can be instructed to invoke {@link String#intern} + * on the strings being parsed into the array. + */ + public static String[] parseStringArray (String source, boolean intern) + { + int tcount = 0, tpos = -1, tstart = 0; + + // empty strings result in zero length arrays + if (source.length() == 0) { + return new String[0]; + } + + // sort out escaped commas + source = source.replace(",,", "%COMMA%"); + + // count up the number of tokens + while ((tpos = source.indexOf(",", tpos+1)) != -1) { + tcount++; + } + + String[] tokens = new String[tcount+1]; + tpos = -1; tcount = 0; + + // do the split + while ((tpos = source.indexOf(",", tpos+1)) != -1) { + tokens[tcount] = source.substring(tstart, tpos); + tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ","); + if (intern) { + tokens[tcount] = tokens[tcount].intern(); + } + tstart = tpos+1; + tcount++; + } + + // grab the last token + tokens[tcount] = source.substring(tstart); + tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ","); + + return tokens; + } + + /** + * @return the supplied string if it is non-null, "" if it is null. + */ + public static String deNull (String value) + { + return (value == null) ? "" : value; + } + + /** + * Generates a string from the supplied bytes that is the HEX encoded representation of those + * bytes. Returns the empty string for a null or empty byte array. + * + * @param bytes the bytes for which we want a string representation. + * @param count the number of bytes to stop at (which will be coerced into being {@code <=} the + * length of the array). + */ + public static String hexlate (byte[] bytes, int count) + { + if (bytes == null) { + return ""; + } + + count = Math.min(count, bytes.length); + char[] chars = new char[count*2]; + + for (int i = 0; i < count; i++) { + int val = bytes[i]; + if (val < 0) { + val += 256; + } + chars[2*i] = XLATE.charAt(val/16); + chars[2*i+1] = XLATE.charAt(val%16); + } + + return new String(chars); + } + + /** + * Generates a string from the supplied bytes that is the HEX encoded representation of those + * bytes. + */ + public static String hexlate (byte[] bytes) + { + return (bytes == null) ? "" : hexlate(bytes, bytes.length); + } + + /** + * Joins an array of strings (or objects which will be converted to strings) into a single + * string separated by commas. + */ + public static String join (Object[] values) + { + return join(values, false); + } + + /** + * Joins an array of strings into a single string, separated by commas, and optionally escaping + * commas that occur in the individual string values such that a subsequent call to {@link + * #parseStringArray} would recreate the string array properly. Any elements in the values + * array that are null will be treated as an empty string. + */ + public static String join (Object[] values, boolean escape) + { + return join(values, ", ", escape); + } + + /** + * Joins the supplied array of strings into a single string separated by the supplied + * separator. + */ + public static String join (Object[] values, String separator) + { + return join(values, separator, false); + } + + /** + * Helper function for the various join methods. + */ + protected static String join (Object[] values, String separator, boolean escape) + { + StringBuilder buf = new StringBuilder(); + int vlength = values.length; + for (int i = 0; i < vlength; i++) { + if (i > 0) { + buf.append(separator); + } + String value = (values[i] == null) ? "" : values[i].toString(); + buf.append((escape) ? value.replace(",", ",,") : value); + } + return buf.toString(); + } + + /** Used by {@link #hexlate} and {@link #unhexlate}. */ + protected static final String XLATE = "0123456789abcdef"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java new file mode 100644 index 0000000..49e4e6e --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java @@ -0,0 +1,114 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.threerings.getdown.data.SysProps; +import static com.threerings.getdown.Log.log; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Version related utilities. + */ +public class VersionUtil +{ + /** + * Reads a version number from a file. + */ + public static long readVersion (File vfile) + { + long fileVersion = -1; + try (BufferedReader bin = + new BufferedReader(new InputStreamReader(new FileInputStream(vfile), UTF_8))) { + String vstr = bin.readLine(); + if (!StringUtil.isBlank(vstr)) { + fileVersion = Long.parseLong(vstr); + } + } catch (Exception e) { + log.info("Unable to read version file: " + e.getMessage()); + } + + return fileVersion; + } + + /** + * Writes a version number to a file. + */ + public static void writeVersion (File vfile, long version) throws IOException + { + try (PrintStream out = new PrintStream(new FileOutputStream(vfile))) { + out.println(version); + } catch (Exception e) { + log.warning("Unable to write version file: " + e.getMessage()); + } + } + + /** + * Parses {@code versStr} using {@code versRegex} into a (long) integer version number. + * @see SysProps#parseJavaVersion + */ + public static long parseJavaVersion (String versRegex, String versStr) + { + Matcher m = Pattern.compile(versRegex).matcher(versStr); + if (!m.matches()) return 0L; + + long vers = 0L; + for (int ii = 1; ii <= m.groupCount(); ii++) { + String valstr = m.group(ii); + int value = (valstr == null) ? 0 : parseInt(valstr); + vers *= 100; + vers += value; + } + return vers; + } + + /** + * Reads and parses the version from the {@code release} file bundled with a JVM. + */ + public static long readReleaseVersion (File relfile, String versRegex) + { + try (BufferedReader in = + new BufferedReader(new InputStreamReader(new FileInputStream(relfile), UTF_8))) { + String line = null, relvers = null; + while ((line = in.readLine()) != null) { + if (line.startsWith("JAVA_VERSION=")) { + relvers = line.substring("JAVA_VERSION=".length()).replace('"', ' ').trim(); + } + } + + if (relvers == null) { + log.warning("No JAVA_VERSION line in 'release' file", "file", relfile); + return 0L; + } + return parseJavaVersion(versRegex, relvers); + + } catch (Exception e) { + log.warning("Failed to read version from 'release' file", "file", relfile, e); + return 0L; + } + } + + private static int parseInt (String str) { + int value = 0; + for (int ii = 0, ll = str.length(); ii < ll; ii++) { + char c = str.charAt(ii); + if (c >= '0' && c <= '9') { + value *= 10; + value += (c - '0'); + } + } + return value; + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java new file mode 100644 index 0000000..d5a3937 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java @@ -0,0 +1,71 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.cache; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +/** + * Validates that cache garbage is collected and deleted correctly. + */ +public class GarbageCollectorTest +{ + @Before public void setupFiles () throws IOException + { + _cachedFile = _folder.newFile("abc123.jar"); + _lastAccessedFile = _folder.newFile("abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX); + } + + @Test public void shouldDeleteCacheEntryIfRetentionPeriodIsReached () + { + gcNow(); + assertFalse(_cachedFile.exists()); + assertFalse(_lastAccessedFile.exists()); + } + + @Test public void shouldDeleteCacheFolderIfFolderIsEmpty () + { + gcNow(); + assertFalse(_folder.getRoot().exists()); + } + + private void gcNow() { + GarbageCollector.collect(_folder.getRoot(), -1); + } + + @Test public void shouldKeepFilesInCacheIfRententionPeriodIsNotReached () + { + GarbageCollector.collect(_folder.getRoot(), TimeUnit.DAYS.toMillis(1)); + assertTrue(_cachedFile.exists()); + assertTrue(_lastAccessedFile.exists()); + } + + @Test public void shouldDeleteCachedFileIfLastAccessedFileIsMissing () + { + assumeTrue(_lastAccessedFile.delete()); + gcNow(); + assertFalse(_cachedFile.exists()); + } + + @Test public void shouldDeleteLastAccessedFileIfCachedFileIsMissing () + { + assumeTrue(_cachedFile.delete()); + gcNow(); + assertFalse(_lastAccessedFile.exists()); + } + + @Rule public TemporaryFolder _folder = new TemporaryFolder(); + + private File _cachedFile; + private File _lastAccessedFile; +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java new file mode 100644 index 0000000..860c72a --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java @@ -0,0 +1,72 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.cache; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.*; + +/** + * Asserts the correct functionality of the {@link ResourceCache}. + */ +public class ResourceCacheTest +{ + @Before public void setupCache () throws IOException { + _fileToCache = _folder.newFile("filetocache.jar"); + _cache = new ResourceCache(_folder.newFolder(".cache")); + } + + @Test public void shouldCacheFile () throws IOException + { + assertEquals("abc123.jar", cacheFile().getName()); + } + + private File cacheFile() throws IOException + { + return _cache.cacheFile(_fileToCache, "abc123", "abc123"); + } + + @Test public void shouldTrackFileUsage () throws IOException + { + String name = "abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX; + File lastAccessedFile = new File(cacheFile().getParentFile(), name); + assertTrue(lastAccessedFile.exists()); + } + + @Test public void shouldNotCacheTheSameFile () throws Exception + { + File cachedFile = cacheFile(); + cachedFile.setLastModified(YESTERDAY); + long expectedLastModified = cachedFile.lastModified(); + // caching it another time + File sameCachedFile = cacheFile(); + assertEquals(expectedLastModified, sameCachedFile.lastModified()); + } + + @Test public void shouldRememberWhenFileWasRequested () throws Exception + { + File cachedFile = cacheFile(); + String name = cachedFile.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX; + File lastAccessedFile = new File(cachedFile.getParentFile(), name); + lastAccessedFile.setLastModified(YESTERDAY); + long lastAccessed = lastAccessedFile.lastModified(); + // caching it another time + cacheFile(); + assertTrue(lastAccessedFile.lastModified() > lastAccessed); + } + + @Rule public TemporaryFolder _folder = new TemporaryFolder(); + + private File _fileToCache; + private ResourceCache _cache; + + private static final long YESTERDAY = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java new file mode 100644 index 0000000..5344f3b --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java @@ -0,0 +1,54 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.LinkedHashSet; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link ClassPath}. + */ +public class ClassPathTest +{ + @Before public void createJarsAndSetupClassPath () throws IOException + { + _firstJar = _folder.newFile("a.jar"); + _secondJar = _folder.newFile("b.jar"); + + LinkedHashSet classPathEntries = new LinkedHashSet(); + classPathEntries.add(_firstJar); + classPathEntries.add(_secondJar); + _classPath = new ClassPath(classPathEntries); + } + + @Test public void shouldCreateValidArgumentString () + { + assertEquals( + _firstJar.getAbsolutePath() + File.pathSeparator + _secondJar.getAbsolutePath(), + _classPath.asArgumentString()); + } + + @Test public void shouldProvideJarUrls () throws MalformedURLException, URISyntaxException + { + URL[] actualUrls = _classPath.asUrls(); + assertEquals(_firstJar, new File(actualUrls[0].toURI())); + assertEquals(_secondJar, new File(actualUrls[1].toURI())); + } + + @Rule public TemporaryFolder _folder = new TemporaryFolder(); + + private File _firstJar, _secondJar; + private ClassPath _classPath; +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java new file mode 100644 index 0000000..6178651 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java @@ -0,0 +1,142 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.io.File; + +import org.junit.*; +import static org.junit.Assert.*; + +public class EnvConfigTest { + + static String CWD = System.getProperty("user.dir"); + static String TESTID = "testid"; + static String TESTBASE = "https://test.com/test"; + + private void debugNotes(List notes) { + for (EnvConfig.Note note : notes) { + System.out.println(note.message); + } + } + + private void checkNoNotes (List notes) { + StringBuilder msg = new StringBuilder(); + for (EnvConfig.Note note : notes) { + if (note.level != EnvConfig.Note.Level.INFO) { + msg.append("\n").append(note.message); + } + } + if (msg.length() > 0) { + fail("Unexpected notes:" + msg.toString()); + } + } + private void checkDir (EnvConfig cfg) { + assertTrue(cfg != null); + assertEquals(new File(CWD), cfg.appDir); + } + private void checkNoAppId (EnvConfig cfg) { + assertNull(cfg.appId); + } + private void checkAppId (EnvConfig cfg, String appId) { + assertEquals(appId, cfg.appId); + } + private void checkAppBase (EnvConfig cfg, String appBase) { + assertEquals(appBase, cfg.appBase); + } + private void checkNoAppBase (EnvConfig cfg) { + assertNull(cfg.appBase); + } + private void checkNoAppArgs (EnvConfig cfg) { + assertTrue(cfg.appArgs.isEmpty()); + } + private void checkAppArgs (EnvConfig cfg, String... args) { + assertEquals(Arrays.asList(args), cfg.appArgs); + } + + @Test public void testArgvDir () { + List notes = new ArrayList<>(); + String[] args = { CWD }; + EnvConfig cfg = EnvConfig.create(args, notes); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkNoAppId(cfg); + checkNoAppBase(cfg); + checkNoAppArgs(cfg); + } + + @Test public void testArgvDirId () { + List notes = new ArrayList<>(); + String[] args = { CWD, TESTID }; + EnvConfig cfg = EnvConfig.create(args, notes); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkAppId(cfg, TESTID); + checkNoAppBase(cfg); + checkNoAppArgs(cfg); + } + + @Test public void testArgvDirArgs () { + List notes = new ArrayList<>(); + String[] args = { CWD, "", "one", "two" }; + EnvConfig cfg = EnvConfig.create(args, notes); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkNoAppId(cfg); + checkNoAppBase(cfg); + checkAppArgs(cfg, "one", "two"); + } + + @Test public void testArgvDirIdArgs () { + List notes = new ArrayList<>(); + String[] args = { CWD, TESTID, "one", "two" }; + EnvConfig cfg = EnvConfig.create(args, notes); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkAppId(cfg, TESTID); + checkNoAppBase(cfg); + checkAppArgs(cfg, "one", "two"); + } + + private EnvConfig sysPropsConfig (List notes, String... keyVals) { + for (int ii = 0; ii < keyVals.length; ii += 2) { + System.setProperty(keyVals[ii], keyVals[ii+1]); + } + EnvConfig cfg = EnvConfig.create(new String[0], notes); + for (int ii = 0; ii < keyVals.length; ii += 2) { + System.clearProperty(keyVals[ii]); + } + return cfg; + } + + @Test public void testSysPropsDir () { + List notes = new ArrayList<>(); + EnvConfig cfg = sysPropsConfig(notes, "appdir", CWD); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkNoAppId(cfg); + checkNoAppBase(cfg); + checkNoAppArgs(cfg); + } + + @Test public void testSysPropsDirIdBase () { + List notes = new ArrayList<>(); + EnvConfig cfg = sysPropsConfig(notes, "appdir", CWD, "appid", TESTID, "appbase", TESTBASE); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkAppId(cfg, TESTID); + checkAppBase(cfg, TESTBASE); + checkNoAppArgs(cfg); + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java new file mode 100644 index 0000000..7f35094 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java @@ -0,0 +1,70 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import static org.junit.Assert.assertEquals; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class PathBuilderTest +{ + @Before public void setupFilesAndResources () throws IOException + { + _firstJarFile = _appdir.newFile("a.jar"); + _secondJarFile = _appdir.newFile("b.jar"); + + when(_firstJar.getFinalTarget()).thenReturn(_firstJarFile); + when(_secondJar.getFinalTarget()).thenReturn(_secondJarFile); + when(_application.getActiveCodeResources()).thenReturn(Arrays.asList(_firstJar, _secondJar)); + when(_application.getAppDir()).thenReturn(_appdir.getRoot()); + } + + @Test public void shouldBuildDefaultClassPath () throws IOException + { + ClassPath classPath = PathBuilder.buildDefaultClassPath(_application); + String expectedClassPath = _firstJarFile.getAbsolutePath() + File.pathSeparator + + _secondJarFile.getAbsolutePath(); + assertEquals(expectedClassPath, classPath.asArgumentString()); + } + + @Test public void shouldBuildCachedClassPath () throws IOException + { + when(_application.getDigest(_firstJar)).thenReturn("first"); + when(_application.getDigest(_secondJar)).thenReturn("second"); + when(_application.getCodeCacheRetentionDays()).thenReturn(1); + + Path firstCachedJarFile = _appdir.getRoot().toPath(). + resolve(PathBuilder.CODE_CACHE_DIR).resolve("fi").resolve("first.jar"); + + Path secondCachedJarFile = _appdir.getRoot().toPath(). + resolve(PathBuilder.CODE_CACHE_DIR).resolve("se").resolve("second.jar"); + + String expectedClassPath = firstCachedJarFile.toAbsolutePath() + File.pathSeparator + + secondCachedJarFile.toAbsolutePath(); + + ClassPath classPath = PathBuilder.buildCachedClassPath(_application); + assertEquals(expectedClassPath, classPath.asArgumentString()); + } + + @Mock protected Application _application; + @Mock protected Resource _firstJar; + @Mock protected Resource _secondJar; + + protected File _firstJarFile, _secondJarFile; + + @Rule public TemporaryFolder _appdir = new TemporaryFolder(); +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java new file mode 100644 index 0000000..042a13f --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java @@ -0,0 +1,63 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import org.junit.*; +import static org.junit.Assert.*; + +public class SysPropsTest { + + @After public void clearProps () { + System.clearProperty("delay"); + System.clearProperty("appbase_domain"); + System.clearProperty("appbase_override"); + } + + private static final String[] APPBASES = { + "http://foobar.com/myapp", + "https://foobar.com/myapp", + "http://foobar.com:8080/myapp", + "https://foobar.com:8080/myapp" + }; + + @Test public void testStartDelay () { + + assertEquals(0, SysProps.startDelay()); + + System.setProperty("delay", "x"); + assertEquals(0, SysProps.startDelay()); + + System.setProperty("delay", "-7"); + assertEquals(0, SysProps.startDelay()); + + System.setProperty("delay", "7"); + assertEquals(7, SysProps.startDelay()); + + System.setProperty("delay", "1440"); + assertEquals(1440, SysProps.startDelay()); + + System.setProperty("delay", "1441"); + assertEquals(1440, SysProps.startDelay()); + } + + @Test public void testAppbaseDomain () { + System.setProperty("appbase_domain", "https://barbaz.com"); + for (String appbase : APPBASES) { + assertEquals("https://barbaz.com/myapp", SysProps.overrideAppbase(appbase)); + } + System.setProperty("appbase_domain", "http://barbaz.com"); + for (String appbase : APPBASES) { + assertEquals("http://barbaz.com/myapp", SysProps.overrideAppbase(appbase)); + } + } + + @Test public void testAppbaseOverride () { + System.setProperty("appbase_override", "https://barbaz.com/newapp"); + for (String appbase : APPBASES) { + assertEquals("https://barbaz.com/newapp", SysProps.overrideAppbase(appbase)); + } + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java new file mode 100644 index 0000000..7aa48ee --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java @@ -0,0 +1,23 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +/** + * Tests {@link Color}. + */ +public class ColorTest +{ + @Test + public void testBrightness() { + assertEquals(0, Color.brightness(0xFF000000), 0.0000001); + assertEquals(1, Color.brightness(0xFFFFFFFF), 0.0000001); + assertEquals(0.0117647, Color.brightness(0xFF010203), 0.0000001); + assertEquals(1, Color.brightness(0xFF00FFC8), 0.0000001); + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java new file mode 100644 index 0000000..cdc5a91 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java @@ -0,0 +1,171 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.Random; + +import org.junit.*; +import static org.junit.Assert.*; + +/** + * Tests {@link Config}. + */ +public class ConfigTest +{ + public static class Pair { + public final String key; + public final String value; + public Pair (String key, String value) { + this.key = key; + this.value = value; + } + } + + public static final Pair[] SIMPLE_PAIRS = { + new Pair("one", "two"), + new Pair("three", "four"), + new Pair("five", "six"), + new Pair("seven", "eight"), + new Pair("nine", "ten"), + }; + + @Test public void testSimplePairs () throws IOException + { + List pairs = Config.parsePairs( + toReader(SIMPLE_PAIRS), Config.createOpts(true)); + for (int ii = 0; ii < SIMPLE_PAIRS.length; ii++) { + assertEquals(SIMPLE_PAIRS[ii].key, pairs.get(ii)[0]); + assertEquals(SIMPLE_PAIRS[ii].value, pairs.get(ii)[1]); + } + } + + @Test public void testQualifiedPairs () throws IOException + { + Pair linux = new Pair("one", "[linux] two"); + Pair mac = new Pair("three", "[mac os x] four"); + Pair linuxAndMac = new Pair("five", "[linux, mac os x] six"); + Pair linux64 = new Pair("seven", "[linux-x86_64] eight"); + Pair linux64s = new Pair("nine", "[linux-x86_64, linux-amd64] ten"); + Pair mac64 = new Pair("eleven", "[mac os x-x86_64] twelve"); + Pair win64 = new Pair("thirteen", "[windows-x86_64] fourteen"); + Pair notWin = new Pair("fifteen", "[!windows] sixteen"); + Pair[] pairs = { linux, mac, linuxAndMac, linux64, linux64s, mac64, win64, notWin }; + + Config.ParseOpts opts = Config.createOpts(false); + opts.osname = "linux"; + opts.osarch = "i386"; + List parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(exists(parsed, notWin.key)); + + opts.osarch = "x86_64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(exists(parsed, linux64.key)); + assertTrue(exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(exists(parsed, notWin.key)); + + opts.osarch = "amd64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(exists(parsed, notWin.key)); + + opts.osname = "mac os x"; + opts.osarch = "x86_64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(!exists(parsed, linux.key)); + assertTrue(exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(exists(parsed, notWin.key)); + + opts.osname = "windows"; + opts.osarch = "i386"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(!exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(!exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(!exists(parsed, notWin.key)); + + opts.osarch = "x86_64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(!exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(!exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(exists(parsed, win64.key)); + assertTrue(!exists(parsed, notWin.key)); + + opts.osarch = "amd64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(!exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(!exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(!exists(parsed, notWin.key)); + } + + protected static boolean exists (List pairs, String key) + { + for (String[] pair : pairs) { + if (pair[0].equals(key)) { + return true; + } + } + return false; + } + + protected static StringReader toReader (Pair[] pairs) + { + StringBuilder builder = new StringBuilder(); + for (Pair pair : pairs) { + // throw some whitespace in to ensure it's trimmed + builder.append(whitespace()).append(pair.key). + append(whitespace()).append("="). + append(whitespace()).append(pair.value). + append(whitespace()).append("\n"); + } + return new StringReader(builder.toString()); + } + + protected static String whitespace () + { + return _rando.nextBoolean() ? " " : ""; + } + + protected static Random _rando = new Random(); +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java new file mode 100644 index 0000000..cfd53a2 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java @@ -0,0 +1,60 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.*; + +/** + * Tests {@link FileUtil}. + */ +public class FileUtilTest +{ + @Test public void testReadLines () throws IOException + { + String data = "This is a test\nof a file with\na few lines\n"; + List lines = FileUtil.readLines(new StringReader(data)); + String[] linesBySplit = data.split("\n"); + assertEquals(linesBySplit.length, lines.size()); + for (int ii = 0; ii < lines.size(); ii++) { + assertEquals(linesBySplit[ii], lines.get(ii)); + } + } + + @Test public void shouldCopyFile () throws IOException + { + File source = _folder.newFile("source.txt"); + File target = new File(_folder.getRoot(), "target.txt"); + assertFalse(target.exists()); + FileUtil.copy(source, target); + assertTrue(target.exists()); + } + + @Test public void shouldRecursivelyWalkOverFilesAndFolders () throws IOException + { + _folder.newFile("a.txt"); + new File(_folder.newFolder("b"), "b.txt").createNewFile(); + + class CountingVisitor implements FileUtil.Visitor { + int fileCount = 0; + @Override public void visit(File file) { + fileCount++; + } + } + CountingVisitor visitor = new CountingVisitor(); + FileUtil.walkTree(_folder.getRoot(), visitor); + assertEquals(3, visitor.fileCount); + } + + @Rule public TemporaryFolder _folder = new TemporaryFolder(); +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java new file mode 100644 index 0000000..703afef --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java @@ -0,0 +1,159 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import static org.junit.Assert.assertEquals; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +/** + * Tests {@link HostWhitelist}. + */ +public class HostWhitelistTest +{ + @Test + public void testVerify () throws MalformedURLException + { + checkCanVerify("foo.com", "http://foo.com", true); + checkCanVerify("foo.com", "http://foo.com/", true); + checkCanVerify("foo.com", "http://foo.com/x/y/z", true); + checkCanVerify("foo.com", "http://www.foo.com", false); + checkCanVerify("foo.com", "http://www.foo.com/", false); + checkCanVerify("foo.com", "http://www.foo.com/x/y/z", false); + checkCanVerify("foo.com", "http://a.b.foo.com", false); + checkCanVerify("foo.com", "http://a.b.foo.com/", false); + checkCanVerify("foo.com", "http://a.b.foo.com/x/y/z", false); + checkCanVerify("foo.com", "http://oo.com", false); + checkCanVerify("foo.com", "http://f.oo.com", false); + checkCanVerify("foo.com", "http://a.f.oo.com", false); + + checkCanVerify("*.foo.com", "http://foo.com", false); + checkCanVerify("*.foo.com", "http://foo.com/", false); + checkCanVerify("*.foo.com", "http://foo.com/x/y/z", false); + checkCanVerify("*.foo.com", "http://www.foo.com", true); + checkCanVerify("*.foo.com", "http://www.foo.com/", true); + checkCanVerify("*.foo.com", "http://www.foo.com/x/y/z", true); + checkCanVerify("*.foo.com", "http://a.b.foo.com", true); + checkCanVerify("*.foo.com", "http://a.b.foo.com/", true); + checkCanVerify("*.foo.com", "http://a.b.foo.com/x/y/z", true); + checkCanVerify("*.foo.com", "http://oo.com", false); + checkCanVerify("*.foo.com", "http://f.oo.com", false); + checkCanVerify("*.foo.com", "http://a.f.oo.com", false); + + checkCanVerify("*.com", "http://foo.com", true); + checkCanVerify("*.com", "http://foo.com/", true); + checkCanVerify("*.com", "http://foo.com/x/y/z", true); + checkCanVerify("*.com", "http://www.foo.com", true); + checkCanVerify("*.com", "http://www.foo.com/", true); + checkCanVerify("*.com", "http://www.foo.com/x/y/z", true); + checkCanVerify("*.com", "http://a.b.foo.com", true); + checkCanVerify("*.com", "http://a.b.foo.com/", true); + checkCanVerify("*.com", "http://a.b.foo.com/x/y/z", true); + checkCanVerify("*.com", "http://oo.com", true); + checkCanVerify("*.com", "http://f.oo.com", true); + checkCanVerify("*.com", "http://a.f.oo.com", true); + + checkCanVerify("*.net", "http://foo.com", false); + checkCanVerify("*.net", "http://foo.com/", false); + checkCanVerify("*.net", "http://foo.com/x/y/z", false); + checkCanVerify("*.net", "http://www.foo.com", false); + checkCanVerify("*.net", "http://www.foo.com/", false); + checkCanVerify("*.net", "http://www.foo.com/x/y/z", false); + checkCanVerify("*.net", "http://a.b.foo.com", false); + checkCanVerify("*.net", "http://a.b.foo.com/", false); + checkCanVerify("*.net", "http://a.b.foo.com/x/y/z", false); + checkCanVerify("*.net", "http://oo.com", false); + checkCanVerify("*.net", "http://f.oo.com", false); + checkCanVerify("*.net", "http://a.f.oo.com", false); + + checkCanVerify("www.*.com", "http://foo.com", false); + checkCanVerify("www.*.com", "http://foo.com/", false); + checkCanVerify("www.*.com", "http://foo.com/x/y/z", false); + checkCanVerify("www.*.com", "http://www.foo.com", true); + checkCanVerify("www.*.com", "http://www.foo.com/", true); + checkCanVerify("www.*.com", "http://www.foo.com/x/y/z", true); + checkCanVerify("www.*.com", "http://a.b.foo.com", false); + checkCanVerify("www.*.com", "http://a.b.foo.com/", false); + checkCanVerify("www.*.com", "http://a.b.foo.com/x/y/z", false); + checkCanVerify("www.*.com", "http://oo.com", false); + checkCanVerify("www.*.com", "http://f.oo.com", false); + checkCanVerify("www.*.com", "http://a.f.oo.com", false); + checkCanVerify("www.*.com", "http://www.a.f.oo.com", true); + + checkCanVerify("foo.*", "http://foo.com", true); + checkCanVerify("foo.*", "http://foo.com/", true); + checkCanVerify("foo.*", "http://foo.com/x/y/z", true); + checkCanVerify("foo.*", "http://www.foo.com", false); + checkCanVerify("foo.*", "http://www.foo.com/", false); + checkCanVerify("foo.*", "http://www.foo.com/x/y/z", false); + checkCanVerify("foo.*", "http://a.b.foo.com", false); + checkCanVerify("foo.*", "http://a.b.foo.com/", false); + checkCanVerify("foo.*", "http://a.b.foo.com/x/y/z", false); + checkCanVerify("foo.*", "http://oo.com", false); + checkCanVerify("foo.*", "http://f.oo.com", false); + checkCanVerify("foo.*", "http://a.f.oo.com", false); + + checkCanVerify("*.foo.*", "http://foo.com", false); + checkCanVerify("*.foo.*", "http://foo.com/", false); + checkCanVerify("*.foo.*", "http://foo.com/x/y/z", false); + checkCanVerify("*.foo.*", "http://www.foo.com", true); + checkCanVerify("*.foo.*", "http://www.foo.com/", true); + checkCanVerify("*.foo.*", "http://www.foo.com/x/y/z", true); + checkCanVerify("*.foo.*", "http://a.b.foo.com", true); + checkCanVerify("*.foo.*", "http://a.b.foo.com/", true); + checkCanVerify("*.foo.*", "http://a.b.foo.com/x/y/z", true); + checkCanVerify("*.foo.*", "http://oo.com", false); + checkCanVerify("*.foo.*", "http://f.oo.com", false); + checkCanVerify("*.foo.*", "http://a.f.oo.com", false); + + checkCanVerify("127.0.0.1", "http://127.0.0.1", true); + checkCanVerify("127.0.0.1", "http://127.0.0.1/", true); + checkCanVerify("127.0.0.1", "http://127.0.0.1/x/y/z", true); + checkCanVerify("*.0.0.1", "http://127.0.0.1/abc", true); + checkCanVerify("127.*.0.1", "http://127.0.0.1/abc", true); + checkCanVerify("127.0.*.1", "http://127.0.0.1/abc", true); + checkCanVerify("127.0.0.*", "http://127.0.0.1/abc", true); + checkCanVerify("127.*.1", "http://127.0.0.1/abc", true); + checkCanVerify("*.0.1", "http://127.0.0.1/abc", true); + checkCanVerify("127.0.*", "http://127.0.0.1/abc", true); + checkCanVerify("*", "http://127.0.0.1/abc", true); + checkCanVerify("127.0.0.2", "http://127.0.0.1", false); + checkCanVerify("127.0.2.1", "http://127.0.0.1", false); + checkCanVerify("127.2.0.1", "http://127.0.0.1", false); + checkCanVerify("222.0.0.1", "http://127.0.0.1", false); + + checkCanVerify("", "http://foo.com", true); + checkCanVerify("", "http://aaa.bbb.net/xyz", true); + checkCanVerify("", "https://127.0.0.1/abc", true); + + checkCanVerify("aaa.bbb.com,xxx.yyy.com, *.jjj.net", "http://aaa.bbb.com/m", true); + checkCanVerify("aaa.bbb.com, xxx.yyy.com,*.jjj.net", "http://xxx.yyy.com/n", true); + checkCanVerify("aaa.bbb.com,xxx.yyy.com, *.jjj.net", "http://www.jjj.net/o", true); + } + + private static void checkCanVerify (String whitelist, String url, boolean expectedToPass) + throws MalformedURLException + { + List w = Arrays.asList(StringUtil.parseStringArray(whitelist)); + URL u = new URL(url); + boolean passed; + + try { + HostWhitelist.verify(w, u); + passed = true; + } catch (MalformedURLException e) { + passed = false; + } + + assertEquals("with whitelist '" + whitelist + "' and URL '" + url + "'", + expectedToPass, passed); + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java new file mode 100644 index 0000000..f70bab9 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java @@ -0,0 +1,28 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import org.junit.Test; + +import static com.threerings.getdown.util.StringUtil.couldBeValidUrl; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests {@link StringUtil}. + */ +public class StringUtilTest +{ + @Test public void testCouldBeValidUrl () + { + assertTrue(couldBeValidUrl("http://www.foo.com/")); + assertTrue(couldBeValidUrl("http://www.foo.com/A-B-C/1_2_3/~bar/q.jsp?x=u+i&y=2;3;4#baz%20baz")); + assertTrue(couldBeValidUrl("https://user:secret@www.foo.com/")); + + assertFalse(couldBeValidUrl("http://www.foo.com & echo hello")); + assertFalse(couldBeValidUrl("http://www.foo.com\"")); + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java new file mode 100644 index 0000000..165fbe3 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java @@ -0,0 +1,53 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class VersionUtilTest { + + @Test + public void shouldParseJavaVersion () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "1.8.0_152"); + assertEquals(1_080_152, version); + } + + @Test + public void shouldParseJavaVersion8 () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "1.8"); + assertEquals(1_080_000, version); + } + + @Test + public void shouldParseJavaVersion9 () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "9"); + assertEquals(9_000_000, version); + } + + @Test + public void shouldParseJavaVersion10 () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "10"); + assertEquals(10_000_000, version); + } + + @Test + public void shouldParseJavaRuntimeVersion () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?(-b\\d+)?", "1.8.0_131-b11"); + assertEquals(108_013_111, version); + } +} diff --git a/getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..1f0955d --- /dev/null +++ b/getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/getdown/src/getdown/core/target/antrun/build-main.xml b/getdown/src/getdown/core/target/antrun/build-main.xml new file mode 100644 index 0000000..6fe3710 --- /dev/null +++ b/getdown/src/getdown/core/target/antrun/build-main.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/classes/LICENSE b/getdown/src/getdown/core/target/classes/LICENSE new file mode 100644 index 0000000..0d9b255 --- /dev/null +++ b/getdown/src/getdown/core/target/classes/LICENSE @@ -0,0 +1,24 @@ +Getdown - application installer, patcher and launcher + +Copyright (C) 2004-2016 Getdown authors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. diff --git a/getdown/src/getdown/core/target/getdown-core-1.8.3-SNAPSHOT.jar b/getdown/src/getdown/core/target/getdown-core-1.8.3-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..545f20bb00aa28ab944a56013ea8edc521f72cb5 GIT binary patch literal 137977 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.ResourceCacheTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.ResourceCacheTest.xml new file mode 100644 index 0000000..67105c2 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.ResourceCacheTest.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.ClassPathTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.ClassPathTest.xml new file mode 100644 index 0000000..fc47a93 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.ClassPathTest.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.EnvConfigTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.EnvConfigTest.xml new file mode 100644 index 0000000..c643f44 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.EnvConfigTest.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.PathBuilderTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.PathBuilderTest.xml new file mode 100644 index 0000000..1bf9d19 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.PathBuilderTest.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.SysPropsTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.SysPropsTest.xml new file mode 100644 index 0000000..cd06cc3 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.SysPropsTest.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ColorTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ColorTest.xml new file mode 100644 index 0000000..1fa7137 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ColorTest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ConfigTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ConfigTest.xml new file mode 100644 index 0000000..6f7d3e9 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ConfigTest.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.FileUtilTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.FileUtilTest.xml new file mode 100644 index 0000000..f9cc27e --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.FileUtilTest.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.HostWhitelistTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.HostWhitelistTest.xml new file mode 100644 index 0000000..5836b6b --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.HostWhitelistTest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.StringUtilTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.StringUtilTest.xml new file mode 100644 index 0000000..e7e4341 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.StringUtilTest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.VersionUtilTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.VersionUtilTest.xml new file mode 100644 index 0000000..f17e12d --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.VersionUtilTest.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.GarbageCollectorTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.GarbageCollectorTest.txt new file mode 100644 index 0000000..92b4d7d --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.GarbageCollectorTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.cache.GarbageCollectorTest +------------------------------------------------------------------------------- +Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.074 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.ResourceCacheTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.ResourceCacheTest.txt new file mode 100644 index 0000000..754966c --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.ResourceCacheTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.cache.ResourceCacheTest +------------------------------------------------------------------------------- +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.ClassPathTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.ClassPathTest.txt new file mode 100644 index 0000000..c7133d5 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.ClassPathTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.data.ClassPathTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.EnvConfigTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.EnvConfigTest.txt new file mode 100644 index 0000000..33de8bc --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.EnvConfigTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.data.EnvConfigTest +------------------------------------------------------------------------------- +Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.PathBuilderTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.PathBuilderTest.txt new file mode 100644 index 0000000..f7d5f9b --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.PathBuilderTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.data.PathBuilderTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.804 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.SysPropsTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.SysPropsTest.txt new file mode 100644 index 0000000..fc7d058 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.SysPropsTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.data.SysPropsTest +------------------------------------------------------------------------------- +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ColorTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ColorTest.txt new file mode 100644 index 0000000..cc44a40 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ColorTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.ColorTest +------------------------------------------------------------------------------- +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ConfigTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ConfigTest.txt new file mode 100644 index 0000000..b8da385 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ConfigTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.ConfigTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.FileUtilTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.FileUtilTest.txt new file mode 100644 index 0000000..bca7cfe --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.FileUtilTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.FileUtilTest +------------------------------------------------------------------------------- +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.HostWhitelistTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.HostWhitelistTest.txt new file mode 100644 index 0000000..bd0d97d --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.HostWhitelistTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.HostWhitelistTest +------------------------------------------------------------------------------- +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.01 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.StringUtilTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.StringUtilTest.txt new file mode 100644 index 0000000..14f28f8 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.StringUtilTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.StringUtilTest +------------------------------------------------------------------------------- +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.VersionUtilTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.VersionUtilTest.txt new file mode 100644 index 0000000..32439d1 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.VersionUtilTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.VersionUtilTest +------------------------------------------------------------------------------- +Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec diff --git a/getdown/src/getdown/core/target/test-classes/mockito-extensions/org.mockito.plugins.MockMaker b/getdown/src/getdown/core/target/test-classes/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..1f0955d --- /dev/null +++ b/getdown/src/getdown/core/target/test-classes/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/getdown/src/getdown/launcher/.project b/getdown/src/getdown/launcher/.project new file mode 100644 index 0000000..d77a6e8 --- /dev/null +++ b/getdown/src/getdown/launcher/.project @@ -0,0 +1,23 @@ + + + getdown-launcher + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..abdea9a --- /dev/null +++ b/getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding/=UTF-8 diff --git a/getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..54e5672 --- /dev/null +++ b/getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/getdown/src/getdown/launcher/pom.xml b/getdown/src/getdown/launcher/pom.xml new file mode 100644 index 0000000..cf94571 --- /dev/null +++ b/getdown/src/getdown/launcher/pom.xml @@ -0,0 +1,176 @@ + + + 4.0.0 + + com.threerings.getdown + getdown + 1.8.3-SNAPSHOT + + + getdown-launcher + jar + Getdown Launcher + The Getdown app updater/launcher + + + + lib-repo + file://${basedir}/../lib + + + + + + com.threerings.getdown + getdown-core + ${project.version} + + + com.samskivert + samskivert + 1.2 + + + jregistrykey + jregistrykey + 1.0 + true + + + + + + + com.github.wvengen + proguard-maven-plugin + 2.0.14 + + + package + proguard + + + + + net.sf.proguard + proguard-base + 6.0.3 + runtime + + + + 6.0.3 + ${project.build.directory} + ${project.build.finalName}.jar + ${project.build.finalName}.jar + + + + com.threerings.getdown + getdown-core + + + com.samskivert + samskivert + + !**/*.java, + !**/swing/RuntimeAdjust*, + !**/swing/util/ButtonUtil*, + !**/util/CalendarUtil*, + !**/util/Calendars*, + !**/util/Log4JLogger*, + !**/util/PrefsConfig*, + !**/util/SignalUtil*, + com/samskivert/Log.class, + **/samskivert/io/**, + **/samskivert/swing/**, + **/samskivert/text/**, + **/samskivert/util/** + + + + jregistrykey + jregistrykey + + + + true + + + + + + + ${rt.jar.path} + + false + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + + com.threerings.getdown.launcher.GetdownApp + + + all-permissions + Getdown + * + * + * + true + + + + + + + + + + + non-mac-jre + + ${java.home}/../lib/rt.jar + + + ${java.home}/../lib/rt.jar + + + + non-mac-jdk + + ${java.home}/lib/rt.jar + + + ${java.home}/lib/rt.jar + + + + java-9-jdk + + ${java.home}/jmods/java.base.jmod + + + + + com.github.wvengen + proguard-maven-plugin + + + ${java.home}/jmods/java.base.jmod + ${java.home}/jmods/java.desktop.jmod + ${java.home}/jmods/java.logging.jmod + ${java.home}/jmods/jdk.jsobject.jmod + + + + + + + + diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java new file mode 100644 index 0000000..dc1e54e --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java @@ -0,0 +1,100 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.launcher; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.samskivert.swing.GroupLayout; +import com.samskivert.swing.Spacer; +import com.samskivert.swing.VGroupLayout; + +import com.threerings.getdown.util.MessageUtil; +import static com.threerings.getdown.Log.log; + +/** + * Displays a confirmation that the user wants to abort installation. + */ +public final class AbortPanel extends JFrame + implements ActionListener +{ + public AbortPanel (Getdown getdown, ResourceBundle msgs) + { + _getdown = getdown; + _msgs = msgs; + + setLayout(new VGroupLayout()); + setResizable(false); + setTitle(get("m.abort_title")); + + JLabel message = new JLabel(get("m.abort_confirm")); + message.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + add(message); + add(new Spacer(5, 5)); + + JPanel row = GroupLayout.makeButtonBox(GroupLayout.CENTER); + JButton button; + row.add(button = new JButton(get("m.abort_ok"))); + button.setActionCommand("ok"); + button.addActionListener(this); + row.add(button = new JButton(get("m.abort_cancel"))); + button.setActionCommand("cancel"); + button.addActionListener(this); + getRootPane().setDefaultButton(button); + add(row); + } + + // documentation inherited + @Override + public Dimension getPreferredSize () + { + // this is annoyingly hardcoded, but we can't just force the width + // or the JLabel will claim a bogus height thinking it can lay its + // text out all on one line which will booch the whole UI's + // preferred size + return new Dimension(300, 200); + } + + // documentation inherited from interface + public void actionPerformed (ActionEvent e) + { + String cmd = e.getActionCommand(); + if (cmd.equals("ok")) { + System.exit(0); + } else { + setVisible(false); + } + } + + /** Used to look up localized messages. */ + protected String get (String key) + { + // if this string is tainted, we don't translate it, instead we + // simply remove the taint character and return it to the caller + if (MessageUtil.isTainted(key)) { + return MessageUtil.untaint(key); + } + try { + return _msgs.getString(key); + } catch (MissingResourceException mre) { + log.warning("Missing translation message '" + key + "'."); + return key; + } + } + + protected Getdown _getdown; + protected ResourceBundle _msgs; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java new file mode 100644 index 0000000..99def4f --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java @@ -0,0 +1,1071 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.launcher; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; + +import javax.imageio.ImageIO; +import javax.swing.AbstractAction; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLayeredPane; + +import com.samskivert.swing.util.SwingUtil; +import com.threerings.getdown.data.*; +import com.threerings.getdown.data.Application.UpdateInterface.Step; +import com.threerings.getdown.net.Downloader; +import com.threerings.getdown.net.HTTPDownloader; +import com.threerings.getdown.tools.Patcher; +import com.threerings.getdown.util.*; + +import static com.threerings.getdown.Log.log; + +/** + * Manages the main control for the Getdown application updater and deployment system. + */ +public abstract class Getdown extends Thread + implements Application.StatusDisplay, RotatingBackgrounds.ImageLoader +{ + public Getdown (EnvConfig envc) + { + super("Getdown"); + try { + // If the silent property exists, install without bringing up any gui. If it equals + // launch, start the application after installing. Otherwise, just install and exit. + _silent = SysProps.silent(); + if (_silent) { + _launchInSilent = SysProps.launchInSilent(); + _noUpdate = SysProps.noUpdate(); + } + // If we're running in a headless environment and have not otherwise customized + // silence, operate without a UI and do launch the app. + if (!_silent && GraphicsEnvironment.isHeadless()) { + log.info("Running in headless JVM, will attempt to operate without UI."); + _silent = true; + _launchInSilent = true; + } + _delay = SysProps.startDelay(); + } catch (SecurityException se) { + // don't freak out, just assume non-silent and no delay; we're probably already + // recovering from a security failure + } + try { + _msgs = ResourceBundle.getBundle("com.threerings.getdown.messages"); + } catch (Exception e) { + // welcome to hell, where java can't cope with a classpath that contains jars that live + // in a directory that contains a !, at least the same bug happens on all platforms + String dir = envc.appDir.toString(); + if (dir.equals(".")) { + dir = System.getProperty("user.dir"); + } + String errmsg = "The directory in which this application is installed:\n" + dir + + "\nis invalid (" + e.getMessage() + "). If the full path to the app directory " + + "contains the '!' character, this will trigger this error."; + fail(errmsg); + } + _app = new Application(envc); + _startup = System.currentTimeMillis(); + } + + /** + * Returns true if there are pending new resources, waiting to be installed. + */ + public boolean isUpdateAvailable () + { + return _readyToInstall && !_toInstallResources.isEmpty(); + } + + /** + * Installs the currently pending new resources. + */ + public void install () throws IOException + { + if (SysProps.noInstall()) { + log.info("Skipping install due to 'no_install' sysprop."); + } else if (_readyToInstall) { + log.info("Installing " + _toInstallResources.size() + " downloaded resources:"); + for (Resource resource : _toInstallResources) { + resource.install(true); + } + _toInstallResources.clear(); + _readyToInstall = false; + log.info("Install completed."); + } else { + log.info("Nothing to install."); + } + } + + @Override + public void run () + { + // if we have no messages, just bail because we're hosed; the error message will be + // displayed to the user already + if (_msgs == null) { + return; + } + + log.info("Getdown starting", "version", Build.version(), "built", Build.time()); + + // determine whether or not we can write to our install directory + File instdir = _app.getLocalPath(""); + if (!instdir.canWrite()) { + String path = instdir.getPath(); + if (path.equals(".")) { + path = System.getProperty("user.dir"); + } + fail(MessageUtil.tcompose("m.readonly_error", path)); + return; + } + + try { + _dead = false; + // if we fail to detect a proxy, but we're allowed to run offline, then go ahead and + // run the app anyway because we're prepared to cope with not being able to update + if (detectProxy() || _app.allowOffline()) { + getdown(); + } else if (_silent) { + log.warning("Need a proxy, but we don't want to bother anyone. Exiting."); + } else { + // create a panel they can use to configure the proxy settings + _container = createContainer(); + // allow them to close the window to abort the proxy configuration + _dead = true; + configureContainer(); + ProxyPanel panel = new ProxyPanel(this, _msgs); + // set up any existing configured proxy + String[] hostPort = ProxyUtil.loadProxy(_app); + panel.setProxy(hostPort[0], hostPort[1]); + _container.add(panel, BorderLayout.CENTER); + showContainer(); + } + + } catch (Exception e) { + log.warning("run() failed.", e); + String msg = e.getMessage(); + if (msg == null) { + msg = MessageUtil.compose("m.unknown_error", _ifc.installError); + } else if (!msg.startsWith("m.")) { + // try to do something sensible based on the type of error + if (e instanceof FileNotFoundException) { + msg = MessageUtil.compose( + "m.missing_resource", MessageUtil.taint(msg), _ifc.installError); + } else { + msg = MessageUtil.compose( + "m.init_error", MessageUtil.taint(msg), _ifc.installError); + } + } + fail(msg); + } + } + + /** + * Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher. + */ + public void configProxy (String host, String port, String username, String password) + { + log.info("User configured proxy", "host", host, "port", port); + + if (!StringUtil.isBlank(host)) { + ProxyUtil.configProxy(_app, host, port, username, password); + } + + // clear out our UI + disposeContainer(); + _container = null; + + // fire up a new thread + new Thread(this).start(); + } + + protected boolean detectProxy () { + if (ProxyUtil.autoDetectProxy(_app)) { + return true; + } + + // otherwise see if we actually need a proxy; first we have to initialize our application + // to get some sort of interface configuration and the appbase URL + log.info("Checking whether we need to use a proxy..."); + try { + readConfig(true); + } catch (IOException ioe) { + // no worries + } + updateStatus("m.detecting_proxy"); + if (!ProxyUtil.canLoadWithoutProxy(_app.getConfigResource().getRemote())) { + return false; + } + + // we got through, so we appear not to require a proxy; make a blank proxy config so that + // we don't go through this whole detection process again next time + log.info("No proxy appears to be needed."); + ProxyUtil.saveProxy(_app, null, null); + return true; + } + + protected void readConfig (boolean preloads) throws IOException { + Config config = _app.init(true); + if (preloads) doPredownloads(_app.getResources()); + _ifc = new Application.UpdateInterface(config); + } + + /** + * Downloads and installs (without verifying) any resources that are marked with a + * {@code PRELOAD} attribute. + * @param resources the full set of resources from the application (the predownloads will be + * extracted from it). + */ + protected void doPredownloads (Collection resources) { + List predownloads = new ArrayList<>(); + for (Resource rsrc : resources) { + if (rsrc.shouldPredownload() && !rsrc.getLocal().exists()) { + predownloads.add(rsrc); + } + } + + try { + download(predownloads); + for (Resource rsrc : predownloads) { + rsrc.install(false); // install but don't validate yet + } + } catch (IOException ioe) { + log.warning("Failed to predownload resources. Continuing...", ioe); + } + } + + /** + * Does the actual application validation, update and launching business. + */ + protected void getdown () + { + try { + // first parses our application deployment file + try { + readConfig(true); + } catch (IOException ioe) { + log.warning("Failed to initialize: " + ioe); + _app.attemptRecovery(this); + // and re-initalize + readConfig(true); + // and force our UI to be recreated with the updated info + createInterfaceAsync(true); + } + if (!_noUpdate && !_app.lockForUpdates()) { + throw new MultipleGetdownRunning(); + } + + // Update the config modtime so a sleeping getdown will notice the change. + File config = _app.getLocalPath(Application.CONFIG_FILE); + if (!config.setLastModified(System.currentTimeMillis())) { + log.warning("Unable to set modtime on config file, will be unable to check for " + + "another instance of getdown running while this one waits."); + } + if (_delay > 0) { + // don't hold the lock while waiting, let another getdown proceed if it starts. + _app.releaseLock(); + // Store the config modtime before waiting the delay amount of time + long lastConfigModtime = config.lastModified(); + log.info("Waiting " + _delay + " minutes before beginning actual work."); + Thread.sleep(_delay * 60 * 1000); + if (lastConfigModtime < config.lastModified()) { + log.warning("getdown.txt was modified while getdown was waiting."); + throw new MultipleGetdownRunning(); + } + } + + // if no_update was specified, directly start the app without updating + if (_noUpdate) { + log.info("Launching without update!"); + launch(); + return; + } + + // we create this tracking counter here so that we properly note the first time through + // the update process whether we previously had validated resources (which means this + // is not a first time install); we may, in the course of updating, wipe out our + // validation markers and revalidate which would make us think we were doing a fresh + // install if we didn't specifically remember that we had validated resources the first + // time through + int[] alreadyValid = new int[1]; + + // we'll keep track of all the resources we unpack + Set unpacked = new HashSet<>(); + + _toInstallResources = new HashSet<>(); + _readyToInstall = false; + + // setStep(Step.START); + for (int ii = 0; ii < MAX_LOOPS; ii++) { + // make sure we have the desired version and that the metadata files are valid... + setStep(Step.VERIFY_METADATA); + setStatusAsync("m.validating", -1, -1L, false); + if (_app.verifyMetadata(this)) { + log.info("Application requires update."); + update(); + // loop back again and reverify the metadata + continue; + } + + // now verify (and download) our resources... + setStep(Step.VERIFY_RESOURCES); + setStatusAsync("m.validating", -1, -1L, false); + Set toDownload = new HashSet<>(); + _app.verifyResources(_progobs, alreadyValid, unpacked, + _toInstallResources, toDownload); + + if (toDownload.size() > 0) { + // we have resources to download, also note them as to-be-installed + for (Resource r : toDownload) { + if (!_toInstallResources.contains(r)) { + _toInstallResources.add(r); + } + } + + try { + // if any of our resources have already been marked valid this is not a + // first time install and we don't want to enable tracking + _enableTracking = (alreadyValid[0] == 0); + reportTrackingEvent("app_start", -1); + + // redownload any that are corrupt or invalid... + log.info(toDownload.size() + " of " + _app.getAllActiveResources().size() + + " rsrcs require update (" + alreadyValid[0] + " assumed valid)."); + setStep(Step.REDOWNLOAD_RESOURCES); + download(toDownload); + + reportTrackingEvent("app_complete", -1); + + } finally { + _enableTracking = false; + } + + // now we'll loop back and try it all again + continue; + } + + // if we aren't running in a JVM that meets our version requirements, either + // complain or attempt to download and install the appropriate version + if (!_app.haveValidJavaVersion()) { + // download and install the necessary version of java, then loop back again and + // reverify everything; if we can't download java; we'll throw an exception + log.info("Attempting to update Java VM..."); + setStep(Step.UPDATE_JAVA); + _enableTracking = true; // always track JVM downloads + try { + updateJava(); + } finally { + _enableTracking = false; + } + continue; + } + + // if we were downloaded in full from another service (say, Steam), we may + // not have unpacked all of our resources yet + if (Boolean.getBoolean("check_unpacked")) { + File ufile = _app.getLocalPath("unpacked.dat"); + long version = -1; + long aversion = _app.getVersion(); + if (!ufile.exists()) { + ufile.createNewFile(); + } else { + version = VersionUtil.readVersion(ufile); + } + + if (version < aversion) { + log.info("Performing unpack", "version", version, "aversion", aversion); + setStep(Step.UNPACK); + updateStatus("m.validating"); + _app.unpackResources(_progobs, unpacked); + try { + VersionUtil.writeVersion(ufile, aversion); + } catch (IOException ioe) { + log.warning("Failed to update unpacked version", ioe); + } + } + } + + // assuming we're not doing anything funny, install the update + _readyToInstall = true; + install(); + + // Only launch if we aren't in silent mode. Some mystery program starting out + // of the blue would be disconcerting. + if (!_silent || _launchInSilent) { + // And another final check for the lock. It'll already be held unless + // we're in silent mode. + _app.lockForUpdates(); + launch(); + } + return; + } + + log.warning("Pants! We couldn't get the job done."); + throw new IOException("m.unable_to_repair"); + + } catch (Exception e) { + log.warning("getdown() failed.", e); + String msg = e.getMessage(); + if (msg == null) { + msg = MessageUtil.compose("m.unknown_error", _ifc.installError); + } else if (!msg.startsWith("m.")) { + // try to do something sensible based on the type of error + if (e instanceof FileNotFoundException) { + msg = MessageUtil.compose( + "m.missing_resource", MessageUtil.taint(msg), _ifc.installError); + } else { + msg = MessageUtil.compose( + "m.init_error", MessageUtil.taint(msg), _ifc.installError); + } + } + // Since we're dead, clear off the 'time remaining' label along with displaying the + // error message + fail(msg); + _app.releaseLock(); + } + } + + // documentation inherited from interface + @Override + public void updateStatus (String message) + { + setStatusAsync(message, -1, -1L, true); + } + + /** + * Load the image at the path. Before trying the exact path/file specified we will look to see + * if we can find a localized version by sticking a {@code _} in front of the "." in + * the filename. + */ + @Override + public BufferedImage loadImage (String path) + { + if (StringUtil.isBlank(path)) { + return null; + } + + File imgpath = null; + try { + // First try for a localized image. + String localeStr = Locale.getDefault().getLanguage(); + imgpath = _app.getLocalPath(path.replace(".", "_" + localeStr + ".")); + return ImageIO.read(imgpath); + } catch (IOException ioe) { + // No biggie, we'll try the generic one. + } + + // If that didn't work, try a generic one. + try { + imgpath = _app.getLocalPath(path); + return ImageIO.read(imgpath); + } catch (IOException ioe2) { + log.warning("Failed to load image", "path", imgpath, "error", ioe2); + return null; + } + } + + /** + * Downloads and installs an Java VM bundled with the application. This is called if we are not + * running with the necessary Java version. + */ + protected void updateJava () + throws IOException + { + Resource vmjar = _app.getJavaVMResource(); + if (vmjar == null) { + throw new IOException("m.java_download_failed"); + } + + reportTrackingEvent("jvm_start", -1); + + updateStatus("m.downloading_java"); + List list = new ArrayList<>(); + list.add(vmjar); + download(list); + + reportTrackingEvent("jvm_unpack", -1); + + updateStatus("m.unpacking_java"); + vmjar.install(true); + + // these only run on non-Windows platforms, so we use Unix file separators + String localJavaDir = LaunchUtil.LOCAL_JAVA_DIR + "/"; + FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "bin/java")); + FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "lib/jspawnhelper")); + FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "lib/amd64/jspawnhelper")); + + // lastly regenerate the .jsa dump file that helps Java to start up faster + String vmpath = LaunchUtil.getJVMPath(_app.getLocalPath("")); + try { + log.info("Regenerating classes.jsa for " + vmpath + "..."); + Runtime.getRuntime().exec(vmpath + " -Xshare:dump"); + } catch (Exception e) { + log.warning("Failed to regenerate .jsa dump file", "error", e); + } + + reportTrackingEvent("jvm_complete", -1); + } + + /** + * Called if the application is determined to be of an old version. + */ + protected void update () + throws IOException + { + // first clear all validation markers + _app.clearValidationMarkers(); + + // attempt to download the patch files + Resource patch = _app.getPatchResource(null); + if (patch != null) { + List list = new ArrayList<>(); + list.add(patch); + + // add the auxiliary group patch files for activated groups + for (Application.AuxGroup aux : _app.getAuxGroups()) { + if (_app.isAuxGroupActive(aux.name)) { + patch = _app.getPatchResource(aux.name); + if (patch != null) { + list.add(patch); + } + } + } + + // show the patch notes button, if applicable + if (!StringUtil.isBlank(_ifc.patchNotesUrl)) { + createInterfaceAsync(false); + EventQueue.invokeLater(new Runnable() { + public void run () { + _patchNotes.setVisible(true); + } + }); + } + + // download the patch files... + setStep(Step.DOWNLOAD); + download(list); + + // and apply them... + setStep(Step.PATCH); + updateStatus("m.patching"); + + long[] sizes = new long[list.size()]; + Arrays.fill(sizes, 1L); + ProgressAggregator pragg = new ProgressAggregator(_progobs, sizes); + int ii = 0; for (Resource prsrc : list) { + ProgressObserver pobs = pragg.startElement(ii++); + try { + // install the patch file (renaming them from _new) + prsrc.install(false); + // now apply the patch + Patcher patcher = new Patcher(); + patcher.patch(prsrc.getLocal().getParentFile(), prsrc.getLocal(), pobs); + } catch (Exception e) { + log.warning("Failed to apply patch", "prsrc", prsrc, e); + } + + // clean up the patch file + if (!FileUtil.deleteHarder(prsrc.getLocal())) { + log.warning("Failed to delete '" + prsrc + "'."); + } + } + } + + // if the patch resource is null, that means something was booched in the application, so + // we skip the patching process but update the metadata which will result in a "brute + // force" upgrade + + // finally update our metadata files... + _app.updateMetadata(); + // ...and reinitialize the application + readConfig(false); + } + + /** + * Called if the application is determined to require resource downloads. + */ + protected void download (Collection resources) + throws IOException + { + // create our user interface + createInterfaceAsync(false); + + Downloader dl = new HTTPDownloader(_app.proxy) { + @Override protected void resolvingDownloads () { + updateStatus("m.resolving"); + } + + @Override protected void downloadProgress (int percent, long remaining) { + // check for another getdown running at 0 and every 10% after that + if (_lastCheck == -1 || percent >= _lastCheck + 10) { + if (_delay > 0) { + // stop the presses if something else is holding the lock + boolean locked = _app.lockForUpdates(); + _app.releaseLock(); + if (locked) abort(); + } + _lastCheck = percent; + } + setStatusAsync("m.downloading", stepToGlobalPercent(percent), remaining, true); + if (percent > 0) { + reportTrackingEvent("progress", percent); + } + } + + @Override protected void downloadFailed (Resource rsrc, Exception e) { + updateStatus(MessageUtil.tcompose("m.failure", e.getMessage())); + log.warning("Download failed", "rsrc", rsrc, e); + } + + /** The last percentage at which we checked for another getdown running, or -1 for not + * having checked at all. */ + protected int _lastCheck = -1; + }; + if (!dl.download(resources, _app.maxConcurrentDownloads())) { + // if we aborted due to detecting another getdown running, we want to report here + throw new MultipleGetdownRunning(); + } + } + + /** + * Called to launch the application if everything is determined to be ready to go. + */ + protected void launch () + { + setStep(Step.LAUNCH); + setStatusAsync("m.launching", stepToGlobalPercent(100), -1L, false); + + try { + if (invokeDirect()) { + // we want to close the Getdown window, as the app is launching + disposeContainer(); + _app.releaseLock(); + _app.invokeDirect(); + + } else { + Process proc; + if (_app.hasOptimumJvmArgs()) { + // if we have "optimum" arguments, we want to try launching with them first + proc = _app.createProcess(true); + + long fallback = System.currentTimeMillis() + FALLBACK_CHECK_TIME; + boolean error = false; + while (fallback > System.currentTimeMillis()) { + try { + error = proc.exitValue() != 0; + break; + } catch (IllegalThreadStateException e) { + Thread.yield(); + } + } + + if (error) { + log.info("Failed to launch with optimum arguments; falling back."); + proc = _app.createProcess(false); + } + } else { + proc = _app.createProcess(false); + } + + // close standard in to avoid choking standard out of the launched process + proc.getInputStream().close(); + // close standard out, since we're not going to write to anything to it anyway + proc.getOutputStream().close(); + + // on Windows 98 and ME we need to stick around and read the output of stderr lest + // the process fill its output buffer and choke, yay! + final InputStream stderr = proc.getErrorStream(); + if (LaunchUtil.mustMonitorChildren()) { + // close our window if it's around + disposeContainer(); + _container = null; + copyStream(stderr, System.err); + log.info("Process exited: " + proc.waitFor()); + + } else { + // spawn a daemon thread that will catch the early bits of stderr in case the + // launch fails + Thread t = new Thread() { + @Override public void run () { + copyStream(stderr, System.err); + } + }; + t.setDaemon(true); + t.start(); + } + } + + // if we have a UI open and we haven't been around for at least 5 seconds (the default + // for min_show_seconds), don't stick a fork in ourselves straight away but give our + // lovely user a chance to see what we're doing + long uptime = System.currentTimeMillis() - _startup; + long minshow = _ifc.minShowSeconds * 1000L; + if (_container != null && uptime < minshow) { + try { + Thread.sleep(minshow - uptime); + } catch (Exception e) { + } + } + + // pump the percent up to 100% + setStatusAsync(null, 100, -1L, false); + exit(0); + + } catch (Exception e) { + log.warning("launch() failed.", e); + } + } + + /** + * Creates our user interface, which we avoid doing unless we actually have to update + * something. NOTE: this happens on the next UI tick, not immediately. + * + * @param reinit - if the interface should be reinitialized if it already exists. + */ + protected void createInterfaceAsync (final boolean reinit) + { + if (_silent || (_container != null && !reinit)) { + return; + } + + EventQueue.invokeLater(new Runnable() { + public void run () { + if (_container == null || reinit) { + if (_container == null) { + _container = createContainer(); + } else { + _container.removeAll(); + } + configureContainer(); + _layers = new JLayeredPane(); + _container.add(_layers, BorderLayout.CENTER); + _patchNotes = new JButton(new AbstractAction(_msgs.getString("m.patch_notes")) { + @Override public void actionPerformed (ActionEvent event) { + showDocument(_ifc.patchNotesUrl); + } + }); + _patchNotes.setFont(StatusPanel.FONT); + _layers.add(_patchNotes); + _status = new StatusPanel(_msgs); + _layers.add(_status); + initInterface(); + } + showContainer(); + } + }); + } + + /** + * Initializes the interface with the current UpdateInterface and backgrounds. + */ + protected void initInterface () + { + RotatingBackgrounds newBackgrounds = getBackground(); + if (_background == null || newBackgrounds.getNumImages() > 0) { + // Leave the old _background in place if there is an old one to leave in place + // and the new getdown.txt didn't yield any images. + _background = newBackgrounds; + } + _status.init(_ifc, _background, getProgressImage()); + Dimension size = _status.getPreferredSize(); + _status.setSize(size); + _layers.setPreferredSize(size); + + _patchNotes.setBounds(_ifc.patchNotes.x, _ifc.patchNotes.y, + _ifc.patchNotes.width, _ifc.patchNotes.height); + _patchNotes.setVisible(false); + + // we were displaying progress while the UI wasn't up. Now that it is, whatever progress + // is left is scaled into a 0-100 DISPLAYED progress. + _uiDisplayPercent = _lastGlobalPercent; + _stepMinPercent = _lastGlobalPercent = 0; + } + + protected RotatingBackgrounds getBackground () + { + if (_ifc.rotatingBackgrounds != null) { + if (_ifc.backgroundImage != null) { + log.warning("ui.background_image and ui.rotating_background were both specified. " + + "The rotating images are being used."); + } + return new RotatingBackgrounds(_ifc.rotatingBackgrounds, _ifc.errorBackground, + Getdown.this); + } else if (_ifc.backgroundImage != null) { + return new RotatingBackgrounds(loadImage(_ifc.backgroundImage)); + } else { + return new RotatingBackgrounds(); + } + } + + protected Image getProgressImage () + { + return loadImage(_ifc.progressImage); + } + + protected void handleWindowClose () + { + if (_dead) { + exit(0); + } else { + if (_abort == null) { + _abort = new AbortPanel(Getdown.this, _msgs); + } + _abort.pack(); + SwingUtil.centerWindow(_abort); + _abort.setVisible(true); + _abort.setState(JFrame.NORMAL); + _abort.requestFocus(); + } + } + + /** + * Update the status to indicate getdown has failed for the reason in message. + */ + protected void fail (String message) + { + _dead = true; + setStatusAsync(message, stepToGlobalPercent(0), -1L, true); + } + + /** + * Set the current step, which will be used to globalize per-step percentages. + */ + protected void setStep (Step step) + { + int finalPercent = -1; + for (Integer perc : _ifc.stepPercentages.get(step)) { + if (perc > _stepMaxPercent) { + finalPercent = perc; + break; + } + } + if (finalPercent == -1) { + // we've gone backwards and this step will be ignored + return; + } + + _stepMaxPercent = finalPercent; + _stepMinPercent = _lastGlobalPercent; + } + + /** + * Convert a step percentage to the global percentage. + */ + protected int stepToGlobalPercent (int percent) + { + int adjustedMaxPercent = + ((_stepMaxPercent - _uiDisplayPercent) * 100) / (100 - _uiDisplayPercent); + _lastGlobalPercent = Math.max(_lastGlobalPercent, + _stepMinPercent + (percent * (adjustedMaxPercent - _stepMinPercent)) / 100); + return _lastGlobalPercent; + } + + /** + * Updates the status. NOTE: this happens on the next UI tick, not immediately. + */ + protected void setStatusAsync (final String message, final int percent, final long remaining, + boolean createUI) + { + if (_status == null && createUI) { + createInterfaceAsync(false); + } + + EventQueue.invokeLater(new Runnable() { + public void run () { + if (_status == null) { + if (message != null) { + log.info("Dropping status '" + message + "'."); + } + return; + } + if (message != null) { + _status.setStatus(message, _dead); + } + if (_dead) { + _status.setProgress(0, -1L); + } else if (percent >= 0) { + _status.setProgress(percent, remaining); + } + } + }); + } + + protected void reportTrackingEvent (String event, int progress) + { + if (!_enableTracking) { + return; + + } else if (progress > 0) { + // we need to make sure we do the right thing if we skip over progress levels + do { + URL url = _app.getTrackingProgressURL(++_reportedProgress); + if (url != null) { + new ProgressReporter(url).start(); + } + } while (_reportedProgress <= progress); + + } else { + URL url = _app.getTrackingURL(event); + if (url != null) { + new ProgressReporter(url).start(); + } + } + } + + /** + * Creates the container in which our user interface will be displayed. + */ + protected abstract Container createContainer (); + + /** + * Configures the interface container based on the latest UI config. + */ + protected abstract void configureContainer (); + + /** + * Shows the container in which our user interface will be displayed. + */ + protected abstract void showContainer (); + + /** + * Disposes the container in which we have our user interface. + */ + protected abstract void disposeContainer (); + + /** + * If this method returns true we will run the application in the same JVM, otherwise we will + * fork off a new JVM. Some options are not supported if we do not fork off a new JVM. + */ + protected boolean invokeDirect () + { + return SysProps.direct(); + } + + /** + * Requests to show the document at the specified URL in a new window. + */ + protected abstract void showDocument (String url); + + /** + * Requests that Getdown exit. + */ + protected abstract void exit (int exitCode); + + /** + * Copies the supplied stream from the specified input to the specified output. Used to copy + * our child processes stderr and stdout to our own stderr and stdout. + */ + protected static void copyStream (InputStream in, PrintStream out) + { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line; + while ((line = reader.readLine()) != null) { + out.print(line); + out.flush(); + } + } catch (IOException ioe) { + log.warning("Failure copying", "in", in, "out", out, "error", ioe); + } + } + + /** Used to fetch a progress report URL. */ + protected class ProgressReporter extends Thread + { + public ProgressReporter (URL url) { + setDaemon(true); + _url = url; + } + + @Override + public void run () { + try { + HttpURLConnection ucon = ConnectionUtil.openHttp(_app.proxy, _url, 0, 0); + + // if we have a tracking cookie configured, configure the request with it + if (_app.getTrackingCookieName() != null && + _app.getTrackingCookieProperty() != null) { + String val = System.getProperty(_app.getTrackingCookieProperty()); + if (val != null) { + ucon.setRequestProperty("Cookie", _app.getTrackingCookieName() + "=" + val); + } + } + + // now request our tracking URL and ensure that we get a non-error response + ucon.connect(); + try { + if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) { + log.warning("Failed to report tracking event", + "url", _url, "rcode", ucon.getResponseCode()); + } + } finally { + ucon.disconnect(); + } + + } catch (IOException ioe) { + log.warning("Failed to report tracking event", "url", _url, "error", ioe); + } + } + + protected URL _url; + } + + /** Used to pass progress on to our user interface. */ + protected ProgressObserver _progobs = new ProgressObserver() { + public void progress (int percent) { + setStatusAsync(null, stepToGlobalPercent(percent), -1L, false); + } + }; + + protected Application _app; + protected Application.UpdateInterface _ifc = new Application.UpdateInterface(Config.EMPTY); + + protected ResourceBundle _msgs; + protected Container _container; + protected JLayeredPane _layers; + protected StatusPanel _status; + protected JButton _patchNotes; + protected AbortPanel _abort; + protected RotatingBackgrounds _background; + + protected boolean _dead; + protected boolean _silent; + protected boolean _launchInSilent; + protected boolean _noUpdate; + protected long _startup; + + protected Set _toInstallResources; + protected boolean _readyToInstall; + + protected boolean _enableTracking = true; + protected int _reportedProgress = 0; + + /** Number of minutes to wait after startup before beginning any real heavy lifting. */ + protected int _delay; + + protected int _stepMaxPercent; + protected int _stepMinPercent; + protected int _lastGlobalPercent; + protected int _uiDisplayPercent; + + protected static final int MAX_LOOPS = 5; + protected static final long FALLBACK_CHECK_TIME = 1000L; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java new file mode 100644 index 0000000..fde79f3 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java @@ -0,0 +1,253 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.launcher; + +import java.awt.Color; +import java.awt.Container; +import java.awt.EventQueue; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.KeyStroke; +import javax.swing.WindowConstants; + +import com.samskivert.swing.util.SwingUtil; +import com.threerings.getdown.data.EnvConfig; +import com.threerings.getdown.data.SysProps; +import com.threerings.getdown.util.LaunchUtil; +import com.threerings.getdown.util.StringUtil; +import static com.threerings.getdown.Log.log; + +/** + * The main application entry point for Getdown. + */ +public class GetdownApp +{ + /** + * The main entry point of the Getdown launcher application. + */ + public static void main (String[] argv) { + try { + start(argv); + } catch (Exception e) { + log.warning("main() failed.", e); + } + } + + /** + * Runs Getdown as an application, using the arguments supplie as {@code argv}. + * @return the {@code Getdown} instance that is running. {@link Getdown#start} will have been + * called on it. + * @throws Exception if anything goes wrong starting Getdown. + */ + public static Getdown start (String[] argv) throws Exception { + List notes = new ArrayList<>(); + EnvConfig envc = EnvConfig.create(argv, notes); + if (envc == null) { + if (!notes.isEmpty()) for (EnvConfig.Note n : notes) System.err.println(n.message); + else System.err.println("Usage: java -jar getdown.jar [app_dir] [app_id] [app args]"); + System.exit(-1); + } + + // pipe our output into a file in the application directory + if (!SysProps.noLogRedir()) { + File logFile = new File(envc.appDir, "launcher.log"); + try { + PrintStream logOut = new PrintStream( + new BufferedOutputStream(new FileOutputStream(logFile)), true); + System.setOut(logOut); + System.setErr(logOut); + } catch (IOException ioe) { + log.warning("Unable to redirect output to '" + logFile + "': " + ioe); + } + } + + // report any notes from reading our env config, and abort if necessary + boolean abort = false; + for (EnvConfig.Note note : notes) { + switch (note.level) { + case INFO: log.info(note.message); break; + case WARN: log.warning(note.message); break; + case ERROR: log.error(note.message); abort = true; break; + } + } + if (abort) System.exit(-1); + + // record a few things for posterity + log.info("------------------ VM Info ------------------"); + log.info("-- OS Name: " + System.getProperty("os.name")); + log.info("-- OS Arch: " + System.getProperty("os.arch")); + log.info("-- OS Vers: " + System.getProperty("os.version")); + log.info("-- Java Vers: " + System.getProperty("java.version")); + log.info("-- Java Home: " + System.getProperty("java.home")); + log.info("-- User Name: " + System.getProperty("user.name")); + log.info("-- User Home: " + System.getProperty("user.home")); + log.info("-- Cur dir: " + System.getProperty("user.dir")); + log.info("---------------------------------------------"); + + Getdown app = new Getdown(envc) { + @Override + protected Container createContainer () { + // create our user interface, and display it + if (_frame == null) { + _frame = new JFrame(""); + _frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing (WindowEvent evt) { + handleWindowClose(); + } + }); + // handle close on ESC + String cancelId = "Cancel"; // $NON-NLS-1$ + _frame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelId); + _frame.getRootPane().getActionMap().put(cancelId, new AbstractAction() { + public void actionPerformed (ActionEvent e) { + handleWindowClose(); + } + }); + // this cannot be called in configureContainer as it is only allowed before the + // frame has been displayed for the first time + _frame.setUndecorated(_ifc.hideDecorations); + _frame.setResizable(false); + } else { + _frame.getContentPane().removeAll(); + } + _frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + return _frame.getContentPane(); + } + + @Override + protected void configureContainer () { + if (_frame == null) return; + + _frame.setTitle(_ifc.name); + + try { + _frame.setBackground(new Color(_ifc.background, true)); + } catch (Exception e) { + log.warning("Failed to set background", "bg", _ifc.background, e); + } + + if (_ifc.iconImages != null) { + ArrayList icons = new ArrayList<>(); + for (String path : _ifc.iconImages) { + Image img = loadImage(path); + if (img == null) { + log.warning("Error loading icon image", "path", path); + } else { + icons.add(img); + } + } + if (icons.isEmpty()) { + log.warning("Failed to load any icons", "iconImages", _ifc.iconImages); + } else { + _frame.setIconImages(icons); + } + } + } + + @Override + protected void showContainer () { + if (_frame != null) { + _frame.pack(); + SwingUtil.centerWindow(_frame); + _frame.setVisible(true); + } + } + + @Override + protected void disposeContainer () { + if (_frame != null) { + _frame.dispose(); + _frame = null; + } + } + + @Override + protected void showDocument (String url) { + if (!StringUtil.couldBeValidUrl(url)) { + // command injection would be possible if we allowed e.g. spaces and double quotes + log.warning("Invalid document URL.", "url", url); + return; + } + String[] cmdarray; + if (LaunchUtil.isWindows()) { + String osName = System.getProperty("os.name", ""); + if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) { + cmdarray = new String[] { + "command.com", "/c", "start", "\"" + url + "\"" }; + } else { + cmdarray = new String[] { + "cmd.exe", "/c", "start", "\"\"", "\"" + url + "\"" }; + } + } else if (LaunchUtil.isMacOS()) { + cmdarray = new String[] { "open", url }; + } else { // Linux, Solaris, etc. + cmdarray = new String[] { "firefox", url }; + } + try { + Runtime.getRuntime().exec(cmdarray); + } catch (Exception e) { + log.warning("Failed to open browser.", "cmdarray", cmdarray, e); + } + } + + @Override + protected void exit (int exitCode) { + // if we're running the app in the same JVM, don't call System.exit, but do + // make double sure that the download window is closed. + if (invokeDirect()) { + disposeContainer(); + } else { + System.exit(exitCode); + } + } + + @Override + protected void fail (String message) { + super.fail(message); + // super.fail causes the UI to be created (if needed) on the next UI tick, so we + // want to wait until that happens before we attempt to redecorate the window + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + // if the frame was set to be undecorated, make window decoration available + // to allow the user to close the window + if (_frame != null && _frame.isUndecorated()) { + _frame.dispose(); + Color bg = _frame.getBackground(); + if (bg != null && bg.getAlpha() < 255) { + // decorated windows do not allow alpha backgrounds + _frame.setBackground( + new Color(bg.getRed(), bg.getGreen(), bg.getBlue())); + } + _frame.setUndecorated(false); + showContainer(); + } + } + }); + } + + protected JFrame _frame; + }; + app.start(); + return app; + } +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java new file mode 100644 index 0000000..5ac7449 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java @@ -0,0 +1,20 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.launcher; + +import java.io.IOException; + +/** + * Thrown when it's detected that multiple instances of the same getdown installer are running. + */ +public class MultipleGetdownRunning extends IOException +{ + public MultipleGetdownRunning () + { + super("m.another_getdown_running"); + } + +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java new file mode 100644 index 0000000..2178273 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java @@ -0,0 +1,195 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.launcher; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import com.samskivert.swing.GroupLayout; +import com.samskivert.swing.Spacer; +import com.samskivert.swing.VGroupLayout; +import com.threerings.getdown.util.MessageUtil; +import static com.threerings.getdown.Log.log; + +/** + * Displays an interface with which the user can configure their proxy + * settings. + */ +public final class ProxyPanel extends JPanel implements ActionListener +{ + public ProxyPanel (Getdown getdown, ResourceBundle msgs) + { + _getdown = getdown; + _msgs = msgs; + + setLayout(new VGroupLayout()); + setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + add(new SaneLabelField(get("m.configure_proxy"))); + add(new Spacer(5, 5)); + + JPanel row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_host")), BorderLayout.WEST); + row.add(_host = new SaneTextField()); + add(row); + + row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_port")), BorderLayout.WEST); + row.add(_port = new SaneTextField()); + add(row); + + add(new Spacer(5, 5)); + + row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_auth_required")), BorderLayout.WEST); + _useAuth = new JCheckBox(); + row.add(_useAuth); + add(row); + + row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_username")), BorderLayout.WEST); + _username = new SaneTextField(); + _username.setEnabled(false); + row.add(_username); + add(row); + + row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_password")), BorderLayout.WEST); + _password = new SanePasswordField(); + _password.setEnabled(false); + row.add(_password); + add(row); + + _useAuth.addItemListener(new ItemListener() { + @Override public void itemStateChanged (ItemEvent event) { + boolean selected = (event.getStateChange() == ItemEvent.SELECTED); + _username.setEnabled(selected); + _password.setEnabled(selected); + } + }); + + add(new Spacer(5, 5)); + + row = GroupLayout.makeButtonBox(GroupLayout.CENTER); + JButton button; + row.add(button = new JButton(get("m.proxy_ok"))); + button.setActionCommand("ok"); + button.addActionListener(this); + row.add(button = new JButton(get("m.proxy_cancel"))); + button.setActionCommand("cancel"); + button.addActionListener(this); + add(row); + } + + public void setProxy (String host, String port) { + if (host != null) { + _host.setText(host); + } + if (port != null) { + _port.setText(port); + } + } + + // documentation inherited + @Override + public void addNotify () + { + super.addNotify(); + _host.requestFocusInWindow(); + } + + // documentation inherited + @Override + public Dimension getPreferredSize () + { + // this is annoyingly hardcoded, but we can't just force the width + // or the JLabel will claim a bogus height thinking it can lay its + // text out all on one line which will booch the whole UI's + // preferred size + return new Dimension(500, 320); + } + + // documentation inherited from interface + @Override + public void actionPerformed (ActionEvent e) + { + String cmd = e.getActionCommand(); + if (cmd.equals("ok")) { + String user = null, pass = null; + if (_useAuth.isSelected()) { + user = _username.getText(); + // we have to keep the proxy password around for every HTTP request, so having it + // in a char[] that gets zeroed out after use is not viable for this use case + pass = new String(_password.getPassword()); + } + _getdown.configProxy(_host.getText(), _port.getText(), user, pass); + } else { + // they canceled, we're outta here + System.exit(0); + } + } + + /** Used to look up localized messages. */ + protected String get (String key) + { + // if this string is tainted, we don't translate it, instead we + // simply remove the taint character and return it to the caller + if (MessageUtil.isTainted(key)) { + return MessageUtil.untaint(key); + } + try { + return _msgs.getString(key); + } catch (MissingResourceException mre) { + log.warning("Missing translation message '" + key + "'."); + return key; + } + } + + protected static class SaneLabelField extends JLabel { + public SaneLabelField(String message) { super(message); } + @Override public Dimension getPreferredSize () { + return clampWidth(super.getPreferredSize(), 200); + } + } + protected static class SaneTextField extends JTextField { + @Override public Dimension getPreferredSize () { + return clampWidth(super.getPreferredSize(), 150); + } + } + protected static class SanePasswordField extends JPasswordField { + @Override public Dimension getPreferredSize () { + return clampWidth(super.getPreferredSize(), 150); + } + } + + protected static Dimension clampWidth (Dimension dim, int minWidth) { + dim.width = Math.max(dim.width, minWidth); + return dim; + } + + protected Getdown _getdown; + protected ResourceBundle _msgs; + + protected JTextField _host; + protected JTextField _port; + protected JCheckBox _useAuth; + protected JTextField _username; + protected JPasswordField _password; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java new file mode 100644 index 0000000..a36b5fa --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java @@ -0,0 +1,210 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.launcher; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.net.Authenticator; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.PasswordAuthentication; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.util.Iterator; +import java.util.ServiceLoader; + +import ca.beq.util.win32.registry.RegistryKey; +import ca.beq.util.win32.registry.RegistryValue; +import ca.beq.util.win32.registry.RootKey; + +import com.threerings.getdown.data.Application; +import com.threerings.getdown.spi.ProxyAuth; +import com.threerings.getdown.util.Config; +import com.threerings.getdown.util.ConnectionUtil; +import com.threerings.getdown.util.LaunchUtil; +import com.threerings.getdown.util.StringUtil; + +import static com.threerings.getdown.Log.log; + +public class ProxyUtil { + + public static boolean autoDetectProxy (Application app) + { + String host = null, port = null; + + // check for a proxy configured via system properties + if (System.getProperty("https.proxyHost") != null) { + host = System.getProperty("https.proxyHost"); + port = System.getProperty("https.proxyPort"); + } + if (StringUtil.isBlank(host) && System.getProperty("http.proxyHost") != null) { + host = System.getProperty("http.proxyHost"); + port = System.getProperty("http.proxyPort"); + } + + // check the Windows registry + if (StringUtil.isBlank(host) && LaunchUtil.isWindows()) { + try { + String rhost = null, rport = null; + boolean enabled = false; + RegistryKey.initialize(); + RegistryKey r = new RegistryKey(RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY); + for (Iterator iter = r.values(); iter.hasNext(); ) { + RegistryValue value = (RegistryValue)iter.next(); + if (value.getName().equals("ProxyEnable")) { + enabled = value.getStringValue().equals("1"); + } + if (value.getName().equals("ProxyServer")) { + String strval = value.getStringValue(); + int cidx = strval.indexOf(":"); + if (cidx != -1) { + rport = strval.substring(cidx+1); + strval = strval.substring(0, cidx); + } + rhost = strval; + } + } + if (enabled) { + host = rhost; + port = rport; + } else { + log.info("Detected no proxy settings in the registry."); + } + } catch (Throwable t) { + log.info("Failed to find proxy settings in Windows registry", "error", t); + } + } + + // look for a proxy.txt file + if (StringUtil.isBlank(host)) { + String[] hostPort = loadProxy(app); + host = hostPort[0]; + port = hostPort[1]; + } + + if (StringUtil.isBlank(host)) { + return false; + } + + // yay, we found a proxy configuration, configure it in the app + initProxy(app, host, port, null, null); + return true; + } + + public static boolean canLoadWithoutProxy (URL rurl) + { + log.info("Testing whether proxy is needed, via: " + rurl); + try { + // try to make a HEAD request for this URL (use short connect and read timeouts) + URLConnection conn = ConnectionUtil.open(Proxy.NO_PROXY, rurl, 5, 5); + if (conn instanceof HttpURLConnection) { + HttpURLConnection hcon = (HttpURLConnection)conn; + try { + hcon.setRequestMethod("HEAD"); + hcon.connect(); + // make sure we got a satisfactory response code + int rcode = hcon.getResponseCode(); + if (rcode == HttpURLConnection.HTTP_PROXY_AUTH || + rcode == HttpURLConnection.HTTP_FORBIDDEN) { + log.warning("Got an 'HTTP credentials needed' response", "code", rcode); + } else { + return true; + } + } finally { + hcon.disconnect(); + } + } else { + // if the appbase is not an HTTP/S URL (like file:), then we don't need a proxy + return true; + } + } catch (IOException ioe) { + log.info("Failed to HEAD " + rurl + ": " + ioe); + log.info("We probably need a proxy, but auto-detection failed."); + } + return false; + } + + public static void configProxy (Application app, String host, String port, + String username, String password) { + // save our proxy host and port in a local file + saveProxy(app, host, port); + + // save our credentials via the SPI + if (!StringUtil.isBlank(username) && !StringUtil.isBlank(password)) { + ServiceLoader loader = ServiceLoader.load(ProxyAuth.class); + Iterator iterator = loader.iterator(); + String appDir = app.getAppDir().getAbsolutePath(); + while (iterator.hasNext()) { + iterator.next().saveCredentials(appDir, username, password); + } + } + + // also configure them in the app + initProxy(app, host, port, username, password); + } + + public static String[] loadProxy (Application app) { + File pfile = app.getLocalPath("proxy.txt"); + if (pfile.exists()) { + try { + Config pconf = Config.parseConfig(pfile, Config.createOpts(false)); + return new String[] { pconf.getString("host"), pconf.getString("port") }; + } catch (IOException ioe) { + log.warning("Failed to read '" + pfile + "': " + ioe); + } + } + return new String[] { null, null}; + } + + public static void saveProxy (Application app, String host, String port) { + File pfile = app.getLocalPath("proxy.txt"); + try (PrintStream pout = new PrintStream(new FileOutputStream(pfile))) { + if (!StringUtil.isBlank(host)) { + pout.println("host = " + host); + } + if (!StringUtil.isBlank(port)) { + pout.println("port = " + port); + } + } catch (IOException ioe) { + log.warning("Error creating proxy file '" + pfile + "': " + ioe); + } + } + + public static void initProxy (Application app, String host, String port, + String username, String password) + { + // check whether we have saved proxy credentials + String appDir = app.getAppDir().getAbsolutePath(); + ServiceLoader loader = ServiceLoader.load(ProxyAuth.class); + Iterator iter = loader.iterator(); + ProxyAuth.Credentials creds = iter.hasNext() ? iter.next().loadCredentials(appDir) : null; + if (creds != null) { + username = creds.username; + password = creds.password; + } + boolean haveCreds = !StringUtil.isBlank(username) && !StringUtil.isBlank(password); + + int pport = StringUtil.isBlank(port) ? 80 : Integer.valueOf(port); + log.info("Using proxy", "host", host, "port", pport, "haveCreds", haveCreds); + app.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, pport)); + + if (haveCreds) { + final String fuser = username; + final char[] fpass = password.toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override protected PasswordAuthentication getPasswordAuthentication () { + return new PasswordAuthentication(fuser, fpass); + } + }); + } + } + + protected static final String PROXY_REGISTRY = + "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java new file mode 100644 index 0000000..d3aa2bd --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java @@ -0,0 +1,132 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.launcher; + +import java.awt.Image; +import java.util.List; + +import static com.threerings.getdown.Log.log; + +public final class RotatingBackgrounds +{ + public interface ImageLoader { + /** Loads and returns the image with the supplied path. */ + public Image loadImage (String path); + } + + /** + * Creates a placeholder if there are no images. Just returns null from getImage every time. + */ + public RotatingBackgrounds () + { + makeEmpty(); + } + + /** Creates a single image background. */ + public RotatingBackgrounds (Image background) + { + percentages = new int[] { 0 }; + minDisplayTime = new int[] { 0 }; + images = new Image[] { background }; + errorImage = images[0]; + } + + /** + * Create a sequence of images to be rotated through from backgrounds. + * + * Each String in backgrounds should be the path to the image, a semicolon, and the minimum + * amount of time to display the image in seconds. Each image will be active for an equal + * percentage of the download process, unless one hasn't been active for its minimum display + * time when the next should be shown. In that case, it's left up until its been there for its + * minimum display time and then the next one gets to come up. + */ + public RotatingBackgrounds (List backgrounds, String errorBackground, ImageLoader loader) + { + percentages = new int[backgrounds.size()]; + minDisplayTime = new int[backgrounds.size()]; + images = new Image[backgrounds.size()]; + for (int ii = 0; ii < backgrounds.size(); ii++) { + String background = backgrounds.get(ii); + String[] pieces = background.split(";"); + if (pieces.length != 2) { + log.warning("Unable to parse background image '" + background + "'"); + makeEmpty(); + return; + } + images[ii] = loader.loadImage(pieces[0]); + try { + minDisplayTime[ii] = Integer.parseInt(pieces[1]); + } catch (NumberFormatException e) { + log.warning("Unable to parse background image display time '" + background + "'"); + makeEmpty(); + return; + } + percentages[ii] = (int)((ii/(float)backgrounds.size()) * 100); + } + if (errorBackground == null) { + errorImage = images[0]; + } else { + errorImage = loader.loadImage(errorBackground); + } + } + + /** + * @return the image to display at the given progress or null if there aren't any. + */ + public Image getImage (int progress) + { + if (images.length == 0) { + return null; + } + long now = System.currentTimeMillis(); + if (current != images.length - 1 + && (current == -1 || (progress >= percentages[current + 1] && + (now - currentDisplayStart) / 1000 > minDisplayTime[current]))) { + current++; + currentDisplayStart = now; + } + return images[current]; + } + + /** + * Returns the image to display if an error has caused getdown to fail. + */ + public Image getErrorImage () + { + return errorImage; + } + + /** + * @return the number of images in this RotatingBackgrounds + */ + public int getNumImages() { + return images.length; + } + + protected void makeEmpty () + { + percentages = new int[] {}; + minDisplayTime = new int[] {}; + images = new Image[] {}; + } + + /** Time at which the currently displayed image was first displayed in millis. */ + protected long currentDisplayStart; + + /** The index of the currently displayed image or -1 if we haven't displayed any. */ + protected int current = -1; + + protected Image[] images; + + /** The image to display if getdown has failed due to an error. */ + protected Image errorImage; + + /** Percentage at which each image should be displayed. */ + protected int[] percentages; + + /** Time to show each image in seconds. */ + protected int[] minDisplayTime; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java new file mode 100644 index 0000000..99f44ca --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java @@ -0,0 +1,396 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.launcher; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.ImageObserver; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import javax.swing.JComponent; +import javax.swing.Timer; + +import com.samskivert.swing.Label; +import com.samskivert.swing.LabelStyleConstants; +import com.samskivert.swing.util.SwingUtil; +import com.samskivert.util.Throttle; + +import com.threerings.getdown.data.Application.UpdateInterface; +import com.threerings.getdown.util.MessageUtil; +import com.threerings.getdown.util.Rectangle; +import com.threerings.getdown.util.StringUtil; + +import static com.threerings.getdown.Log.log; + +/** + * Displays download and patching status. + */ +public final class StatusPanel extends JComponent + implements ImageObserver +{ + public StatusPanel (ResourceBundle msgs) + { + _msgs = msgs; + + // Add a bit of "throbbing" to the display by updating the number of dots displayed after + // our status. This lets users know things are still working. + _timer = new Timer(1000, + new ActionListener() { + public void actionPerformed (ActionEvent event) { + if (_status != null && !_displayError) { + _statusDots = (_statusDots % 3) + 1; // 1, 2, 3, 1, 2, 3, etc. + updateStatusLabel(); + } + } + }); + } + + public void init (UpdateInterface ifc, RotatingBackgrounds bg, Image barimg) + { + _ifc = ifc; + _bg = bg; + Image img = _bg.getImage(_progress); + int width = img == null ? -1 : img.getWidth(this); + int height = img == null ? -1 : img.getHeight(this); + if (width == -1 || height == -1) { + Rectangle bounds = ifc.progress.union(ifc.status); + // assume the x inset defines the frame padding; add it on the left, right, and bottom + _psize = new Dimension(bounds.x + bounds.width + bounds.x, + bounds.y + bounds.height + bounds.x); + } else { + _psize = new Dimension(width, height); + } + _barimg = barimg; + invalidate(); + } + + @Override + public boolean imageUpdate (Image img, int infoflags, int x, int y, int width, int height) + { + boolean updated = false; + if ((infoflags & WIDTH) != 0) { + _psize.width = width; + updated = true; + } + if ((infoflags & HEIGHT) != 0) { + _psize.height = height; + updated = true; + } + if (updated) { + invalidate(); + setSize(_psize); + getParent().setSize(_psize); + } + return (infoflags & ALLBITS) == 0; + } + + /** + * Adjusts the progress display to the specified percentage. + */ + public void setProgress (int percent, long remaining) + { + boolean needsRepaint = false; + + // maybe update the progress label + if (_progress != percent) { + _progress = percent; + if (!_ifc.hideProgressText) { + String msg = MessageFormat.format(get("m.complete"), percent); + _newplab = createLabel(msg, new Color(_ifc.progressText, true)); + } + needsRepaint = true; + } + + // maybe update the remaining label + if (remaining > 1) { + // skip this estimate if it's been less than a second since our last one came in + if (!_rthrottle.throttleOp()) { + _remain[_ridx++%_remain.length] = remaining; + } + + // smooth the remaining time by taking the trailing average of the last four values + remaining = 0; + int values = Math.min(_ridx, _remain.length); + for (int ii = 0; ii < values; ii++) { + remaining += _remain[ii]; + } + remaining /= values; + + if (!_ifc.hideProgressText) { + // now compute our display value + int minutes = (int)(remaining / 60), seconds = (int)(remaining % 60); + String remstr = minutes + ":" + ((seconds < 10) ? "0" : "") + seconds; + String msg = MessageFormat.format(get("m.remain"), remstr); + _newrlab = createLabel(msg, new Color(_ifc.statusText, true)); + } + needsRepaint = true; + + } else if (_rlabel != null || _newrlab != null) { + _rthrottle = new Throttle(1, 1000); + _ridx = 0; + _newrlab = _rlabel = null; + needsRepaint = true; + } + + if (needsRepaint) { + repaint(); + } + } + + /** + * Displays the specified status string. + */ + public void setStatus (String status, boolean displayError) + { + _status = xlate(status); + _displayError = displayError; + updateStatusLabel(); + } + + /** + * Stop the throbbing. + */ + public void stopThrob () + { + _timer.stop(); + _statusDots = 3; + updateStatusLabel(); + } + + @Override + public void addNotify () + { + super.addNotify(); + _timer.start(); + } + + @Override + public void removeNotify () + { + _timer.stop(); + super.removeNotify(); + } + + // documentation inherited + @Override + public void paintComponent (Graphics g) + { + super.paintComponent(g); + Graphics2D gfx = (Graphics2D)g; + + // attempt to draw a background image... + Image img; + if (_displayError) { + img = _bg.getErrorImage(); + } else { + img = _bg.getImage(_progress); + } + if (img != null) { + gfx.drawImage(img, 0, 0, this); + } + + Object oalias = SwingUtil.activateAntiAliasing(gfx); + + // if we have new labels; lay them out + if (_newlab != null) { + _newlab.layout(gfx); + _label = _newlab; + _newlab = null; + } + if (_newplab != null) { + _newplab.layout(gfx); + _plabel = _newplab; + _newplab = null; + } + if (_newrlab != null) { + _newrlab.layout(gfx); + _rlabel = _newrlab; + _newrlab = null; + } + + if (_barimg != null) { + gfx.setClip(_ifc.progress.x, _ifc.progress.y, + _progress * _ifc.progress.width / 100, + _ifc.progress.height); + gfx.drawImage(_barimg, _ifc.progress.x, _ifc.progress.y, null); + gfx.setClip(null); + } else { + gfx.setColor(new Color(_ifc.progressBar, true)); + gfx.fillRect(_ifc.progress.x, _ifc.progress.y, + _progress * _ifc.progress.width / 100, + _ifc.progress.height); + } + + if (_plabel != null) { + int xmarg = (_ifc.progress.width - _plabel.getSize().width)/2; + int ymarg = (_ifc.progress.height - _plabel.getSize().height)/2; + _plabel.render(gfx, _ifc.progress.x + xmarg, _ifc.progress.y + ymarg); + } + + if (_label != null) { + _label.render(gfx, _ifc.status.x, getStatusY(_label)); + } + + if (_rlabel != null) { + // put the remaining label at the end of the status area. This could be dangerous + // but I think the only time we would display it is with small statuses. + int x = _ifc.status.x + _ifc.status.width - _rlabel.getSize().width; + _rlabel.render(gfx, x, getStatusY(_rlabel)); + } + + SwingUtil.restoreAntiAliasing(gfx, oalias); + } + + // documentation inherited + @Override + public Dimension getPreferredSize () + { + return _psize; + } + + /** + * Update the status label. + */ + protected void updateStatusLabel () + { + String status = _status; + if (!_displayError) { + for (int ii = 0; ii < _statusDots; ii++) { + status += " ."; + } + } + _newlab = createLabel(status, new Color(_ifc.statusText, true)); + // set the width of the label to the width specified + int width = _ifc.status.width; + if (width == 0) { + // unless we had trouble reading that width, in which case use the entire window + width = getWidth(); + } + // but the window itself might not be initialized and have a width of 0 + if (width > 0) { + _newlab.setTargetWidth(width); + } + repaint(); + } + + /** + * Get the y coordinate of a label in the status area. + */ + protected int getStatusY (Label label) + { + // if the status region is higher than the progress region, we + // want to align the label with the bottom of its region + // rather than the top + if (_ifc.status.y > _ifc.progress.y) { + return _ifc.status.y; + } + return _ifc.status.y + (_ifc.status.height - label.getSize().height); + } + + /** + * Create a label, taking care of adding the shadow if needed. + */ + protected Label createLabel (String text, Color color) + { + Label label = new Label(text, color, FONT); + if (_ifc.textShadow != 0) { + label.setAlternateColor(new Color(_ifc.textShadow, true)); + label.setStyle(LabelStyleConstants.SHADOW); + } + return label; + } + + /** Used by {@link #setStatus}. */ + protected String xlate (String compoundKey) + { + // to be more efficient about creating unnecessary objects, we + // do some checking before splitting + int tidx = compoundKey.indexOf('|'); + if (tidx == -1) { + return get(compoundKey); + + } else { + String key = compoundKey.substring(0, tidx); + String argstr = compoundKey.substring(tidx+1); + String[] args = argstr.split("\\|"); + // unescape and translate the arguments + for (int i = 0; i < args.length; i++) { + // if the argument is tainted, do no further translation + // (it might contain |s or other fun stuff) + if (MessageUtil.isTainted(args[i])) { + args[i] = MessageUtil.unescape(MessageUtil.untaint(args[i])); + } else { + args[i] = xlate(MessageUtil.unescape(args[i])); + } + } + return get(key, args); + } + } + + /** Used by {@link #setStatus}. */ + protected String get (String key, String[] args) + { + String msg = get(key); + if (msg != null) return MessageFormat.format(MessageUtil.escape(msg), (Object[])args); + return key + String.valueOf(Arrays.asList(args)); + } + + /** Used by {@link #setStatus}, and {@link #setProgress}. */ + protected String get (String key) + { + // if we have no _msgs that means we're probably recovering from a + // failure to load the translation messages in the first place, so + // just give them their key back because it's probably an english + // string; whee! + if (_msgs == null) { + return key; + } + + // if this string is tainted, we don't translate it, instead we + // simply remove the taint character and return it to the caller + if (MessageUtil.isTainted(key)) { + return MessageUtil.untaint(key); + } + try { + return _msgs.getString(key); + } catch (MissingResourceException mre) { + log.warning("Missing translation message '" + key + "'."); + return key; + } + } + + protected Image _barimg; + protected RotatingBackgrounds _bg; + protected Dimension _psize; + + protected ResourceBundle _msgs; + + protected int _progress = -1; + protected String _status; + protected int _statusDots = 1; + protected boolean _displayError; + protected Label _label, _newlab; + protected Label _plabel, _newplab; + protected Label _rlabel, _newrlab; + + protected UpdateInterface _ifc; + protected Timer _timer; + + protected long[] _remain = new long[4]; + protected int _ridx; + protected Throttle _rthrottle = new Throttle(1, 1000L); + + protected static final Font FONT = new Font("SansSerif", Font.BOLD, 12); +} diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties new file mode 100644 index 0000000..19b2999 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties @@ -0,0 +1,110 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Abort installation? +m.abort_confirm = Are you sure you want to stop installation? \ + You can resume at a later time by running the application again. +m.abort_ok = Quit +m.abort_cancel = Continue installation + +m.detecting_proxy = Trying to auto-detect proxy settings + +m.configure_proxy = We were unable to connect to the application server to download data. \ +

Please make sure that no virus scanner or firewall is blocking network communicaton with \ + the server. \ +

Your computer may access the Internet through a proxy and we were unable to automatically \ + detect your proxy settings. If you know your proxy settings, you can enter them below. + +m.proxy_extra = If you are sure that you don't use a proxy then \ + perhaps there is a temporary Internet outage that is preventing us from \ + communicating with the servers. In this case, you can cancel and try \ + installing again later. + +m.proxy_host = Proxy IP +m.proxy_port = Proxy port +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = Cancel + +m.downloading_java = Downloading Java Virtual Machine +m.unpacking_java = Unpacking Java Virtual Machine + +m.resolving = Resolving downloads +m.downloading = Downloading data +m.failure = Download failed: {0} + +m.checking = Checking for update +m.validating = Validating +m.patching = Patching +m.launching = Launching + +m.patch_notes = Patch Notes +m.play_again = Play Again + +m.complete = {0}% complete +m.remain = {0} remaining + +m.updating_metadata = Downloading control files + +m.init_failed = Our configuration file is missing or corrupt. Attempting \ + to download a new copy... + +m.java_download_failed = We were unable to automatically download the \ + necessary version of Java for your computer.\n\n\ + Please go to www.java.com and download the latest version of \ + Java, then try running the application again. + +m.java_unpack_failed = We were unable to unpack an updated version of \ + Java. Please make sure you have at least 100 MB of free space on your \ + harddrive and try running the application again.\n\n\ + If that does not solve the problem, go to www.java.com and download and \ + install the latest version of Java and try again. + +m.unable_to_repair = We were unable to download the necessary files after \ + five attempts. You can try running the application again, but if it \ + fails you may need to uninstall and reinstall. + +m.unknown_error = The application has failed to launch due to some strange \ + error from which we could not recover. Please visit\n{0} for information on \ + how to recover. +m.init_error = The application has failed to launch due to the following \ + error:\n{0}\n\nPlease visit\n{1} for \ + information on how to handle such problems. + +m.readonly_error = The directory in which this application is installed: \ + \n{0}\nis read-only. Please install the application into a directory where \ + you have write access. + +m.missing_resource = The application has failed to launch due to a missing \ + resource:\n{0}\n\nPlease visit\n{1} for information on how to handle such \ + problems. + +m.insufficient_permissions_error = You did not accept this application's \ + digital signature. If you want to run the application, you will need to accept \ + its digital signature.\n\nTo do so, you will need to quit your web browser, \ + restart it, and return to this web page to relaunch the application. When the \ + security dialog is shown, click the button to accept the digital signature \ + and grant this application the privileges it needs to run. + +m.corrupt_digest_signature_error = We couldn't verify the application's digital \ + signature.\nPlease check that you are launching the application from\nthe \ + correct website. + +m.default_install_error = the support section of the website + +m.another_getdown_running = Multiple instances of this application's \ + installer are running. This one will stop and let another complete. + +m.applet_stopped = Getdown's applet was told to stop working. + +# application/digest errors +m.missing_appbase = The configuration file is missing the 'appbase'. +m.invalid_version = The configuration file specifies an invalid version. +m.invalid_appbase = The configuration file specifies an invalid 'appbase'. +m.missing_class = The configuration file is missing the application class. +m.missing_code = The configuration file specifies no code resources. +m.invalid_digest_file = The digest file is invalid. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties new file mode 100644 index 0000000..8e36835 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties @@ -0,0 +1,116 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Installation abbrechen? +m.abort_confirm = Bist du sicher, dass du die Installation abbrechen \ +m\u00f6chtest? \ + Du kannst sp\u00e4ter fortfahren, indem du die Anwendung erneut \ +ausf\u00fchrst. +m.abort_ok = Beenden +m.abort_cancel = Installation fortsetzen + +m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln + +m.configure_proxy = Es konnte keine Verbindung zum Applikations-Server aufgebaut werden. \ +

Bitte kontrollieren Sie die Proxyeinstellungen und stellen Sie sicher, dass keine lokal oder \ + im Netzwerk betriebene Sicherheitsanwendung (Virenscanner, Firewall, etc.) die Kommunikation \ + mit dem Server blockiert.
\ + Wenn kein Proxy verwendet werden soll, l\u00f6schen Sie bitte alle Eintr\u00e4ge in den unten \ + stehenden Feldern und klicken sie auf OK. + +m.proxy_extra = Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \ + an Ihren Administrator. + +m.proxy_host = Proxy-Adresse +m.proxy_port = Proxy-Port +m.proxy_username = Benutzername +m.proxy_password = Passwort +m.proxy_auth_required = Authentisierung erforderlich +m.proxy_ok = OK +m.proxy_cancel = Abbrechen + +m.downloading_java = Lade Java Virtual Machine herunter +m.unpacking_java = Entpacke Java Virtual Machine + +m.resolving = Bereite Download vor +m.downloading = Lade Daten herunter +m.failure = Download fehlgeschlagen: {0} + +m.checking = Suche nach Updates +m.validating = Validiere Download +m.patching = Patche +m.launching = Starte + +m.patch_notes = Patchnotes + +m.complete = {0}% abgeschlossen +m.remain = {0} \u00fcbrig + +m.updating_metadata = Lade Steuerungsdateien herunter + +m.init_failed = Unsere Konfigurationsdatei fehlt oder ist besch\u00e4digt. \ +Versuche, eine neue Kopie herunterzuladen... + +m.java_download_failed = Wir konnten die notwendige Javaversion f\u00fcr deinen \ +Computer nicht automatisch herunterladen. \n\n \ +Bitte auf www.java.com die aktuelle Javaversion herunterladen und dann die \ +Anwendung erneut starten. + +m.java_unpack_failed = Wir konnten die aktualisierte Javaversion nicht \ +entpacken. Bitte stelle sicher, dass wenigstens 100MB Platz auf der \ +Festplatte frei sind und versuche dann die Anwendung erneut zu \ +starten.\n\n\ \ +Falls das das Problem nicht beseitigt, bitte auf www.java.com die aktuelle \ +Javaversion herunterladen und installieren und dann erneut versuchen. + +m.unable_to_repair = Wir konnten die notwendigen Dateien nach 5 Versuchen \ +nicht herunterladen. Du kannst versuchen, die Anwendung erneut zu starten, \ +aber wenn dies erneut fehlschl\u00e4gt, musst du die Anwendung deinstallieren \ +und erneut installieren. + +m.unknown_error = Die Anwendung konnte wegen eines unbekannten Fehlers \ +nicht gestartet werden. Bitte auf \n{0} weiterlesen. + +m.init_error = Die Anwendung konnte wegen folgendem Fehler nicht gestartet \ +werden:\n{0}\n\n Bitte auf \n{1} weiterlesen, um zu erfahren, wie bei \ +solchen Problemen vorzugehen ist. + +m.readonly_error = Das Verzeichnis, in dem die Anwendung installiert ist: \ + \n{0}\nist nicht schreibberechtigt. Bitte in ein Verzeichnis mit \ +Schreibzugriff installieren. + +m.missing_resource = Die Anwendung konnte nicht gestartet werden, da die \ +folgende Quelle nicht gefunden wurde:\n{0}\n\n\ Bitte auf \n{1} \ +weiterlesen, um zu erfahren, wie bei solchen Problemen vorzugehen ist. + +m.insufficient_permissions_error = Du hast die digitale Signatur dieser \ +Anwendung nicht akzeptiert. Falls du diese Anwendung benutzen willst, \ +musst du ihre digitale Signatur akzeptieren. \n\Um das zu tun, musst du \ +deinen Browser beenden, neu starten und erneut die Anwendung von dieser \ +Webseite aus starten. Wenn die Sicherheitsabfrage erscheint, bitte die \ +digitale Signatur akzeptieren, um der Anwendung die n\u00f6tigen Rechte zu \ +geben, die sie braucht, um zu laufen. + +m.corrupt_digest_signature_error = Wir konnten die digitale Signatur \ +dieser Anwendung nicht \u00fcberpr\u00fcfen.\nBitte \u00fcberpr\u00fcfe, ob du die Anwendung \ +von der richtigen Webseite aus startest. + +m.default_install_error = der Support-Webseite + +m.another_getdown_running = Diese Installationsanwendung l\u00e4uft in mehreren \ +Instanzen. Diese Instanz wird sich beenden und eine andere Instanz den \ +Vorgang erledigen lassen. + +m.applet_stopped = Die Anwendung wurde beendet. + + +# application/digest errors +m.missing_appbase = In der Konfigurationsdatei fehlt die 'appbase'. +m.invalid_version = In der Konfigurationsdatei steht die falsche Version. +m.invalid_appbase = In der Konfigurationsdatei steht die falsche 'appbase'. +m.missing_class = In der Konfigurationsdatei fehlt die Anwendungsklasse. +m.missing_code = Die Konfigurationsdatei enth\u00e4lt keine Codequellen. +m.invalid_digest_file = Die Hashwertedatei ist ung\u00fcltig. + diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties new file mode 100644 index 0000000..609b025 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties @@ -0,0 +1,115 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \u00bfCancelar la instalaci\u00f3n? +m.abort_confirm = \u00bfEst\u00e1s seguro de querer cancelar la instalaci\u00f3n? \ + Puedes continuarla despu\u00e9s si corres de nuevo la aplicaci\u00f3n. +m.abort_ok = Cancelar +m.abort_cancel = Continuar la instalaci\u00f3n + +m.detecting_proxy = Detectando autom\u00e1ticamente la configuraci\u00f3n proxy + +m.configure_proxy = No ha sido posible conectar con nuestros servidores para \ + descargar los datos del juego. \ +

\ +

Es posible que tu computadora tenga acceso al Internet por medio de un proxy por lo que \ + no ha sido posible detectar autom\u00e1ticamente tu configuraci\u00f3n. Si conoces tu \ + configuraci\u00f3n proxy, puedes anotarla abajo. + +m.proxy_extra = Si est\u00e1s seguro de que no tienes un proxy entonces \ + tal vez exista un falla temporal en el Internet que est\u00e1 evitando que podamos \ + comunicarnos con los servidores. En este caso, puedes cancelar e intentar \ + instalarla de nuevo m\u00e1s tarde. + +m.proxy_host = IP proxy +m.proxy_port = Puerto proxy +m.proxy_username = Nombre de usuario +m.proxy_password = Contrase\u00f1a +m.proxy_auth_required = Autenticacion requerida +m.proxy_ok = OK +m.proxy_cancel = Cancelar + +m.downloading_java = Descargando Java Virtual Machine +m.unpacking_java = Desempacando Java Virtual Machine + +m.resolving = Resolviendo descarga +m.downloading = Descargando datos +m.failure = Descarga fallida: {0} + +m.checking = Buscando actualizaciones +m.validating = Validando +m.patching = Parchando +m.launching = Lanzando + +m.patch_notes = Notas del parche + +m.complete = {0}% completado +m.remain = {0} restante + +m.updating_metadata = Descargando los archivos de control + +m.init_failed = Un archivo de configuraci\u00f3n est\u00e1 faltante o est\u00e1 corrupto. Intentando \ + descargar una nueva copia... + +m.java_download_failed = No ha sido posible descargar autom\u00e1ticamente la \ + versi\u00f3n de Java necesaria para tu computadora.\n\n\ + Por favor ve a www.java.com y descarga la \u00faltima versi\u00f3n de \ + Java, despu\u00e9s intenta correr de nuevo la aplicaci\u00f3n. + +m.java_unpack_failed = No ha sido posible desempacar una versi\u00f3n actualizada de \ + Java. Por favor aseg\u00farate de tener al menos 100 MB de espacio libre en tu \ + disco duro e intenta correr de nuevo la aplicaci\u00f3n.\n\n\ + Si eso no soluciona el problema, ve a www.java.com y descarga e \ + instala la \u00faltima versi\u00f3n de Java e intenta de nuevo. + +m.unable_to_repair = No ha sido posible descargar los archivos necesarios despu\u00e9s de \ + cinco intentos. Puedes intentar correr de nuevo la aplicaci\u00f3n, pero si falla \ + de nuevo podr\u00edas necesitar desinstalar y reinstalar. + +m.unknown_error = La aplicaci\u00f3n no ha podido iniciar debido a un extra\u00f1o \ + error del que no se pudo recobrar. Por favor visita\n{0} para ver informaci\u00f3n acerca \ + de como recuperarla. +m.init_error = La aplicaci\u00f3n no ha podido iniciar debido al siguiente \ + error:\n{0}\n\nPor favor visita\n{1} para \ + ver informaci\u00f3n acerca de como manejar ese tipo de problemas. + +m.readonly_error = El directorio en el que esta aplicaci\u00f3n est\u00e1 instalada: \ + \n{0}\nes solo lectura. Por favor instala la aplicaci\u00f3n en un directorio en el cual \ + tengas acceso de escritura. + +m.missing_resource = La aplicaci\u00f3n no ha podido iniciar debido a un recurso \ + faltante:\n{0}\n\nPor favor visita\n{1} para informaci\u00f3n acerca de como solucionar \ + estos problemas. + +m.insufficient_permissions_error = No aceptaste la firma digital de \ + esta aplicaci\u00f3n. Si quieres correr la aplicaci\u00f3n, necesitas aceptar \ + su firma digital.\n\nPara hacerlo, necesitas cerrar tu navegador, \ + reiniciarlo, y regresar a esta p\u00e1gina web para reiniciar la aplicaci\u00f3n. Cuando se muestre \ + el di\u00e1logo de seguridad, haz clic en el bot\u00f3n para aceptar la firmar digital \ + y otorgar a esta aplicaci\u00f3n los privilegios que necesita para correr. + +m.corrupt_digest_signature_error = No pudimos verificar la firma digital \ + de la aplicaci\u00f3n.\nPor favor revisa que est\u00e9s lanzando la aplicaci\u00f3n desde\nel \ + sitio web correcto. + +m.default_install_error = la secci\u00f3n de asistencia de este sitio web + +m.another_getdown_running = Est\u00e1n corriendo m\u00faltiples instancias de \ + este instalador. Este se detendr\u00e1 para permitir que otra contin\u00fae. + +m.applet_stopped = Se le dijo al applet de Getdown que dejara de trabajar. + +# application/digest errors +m.missing_appbase = Al archivo de configuraci\u00f3n le falta el 'appbase'. +m.invalid_version = El archivo de configuraci\u00f3n especifica una versi\u00f3n no v\u00e1lida. +m.invalid_appbase = El archivo de configuraci\u00f3n especifica un 'appbase' no v\u00e1lido. +m.missing_class = Al archivo de configuraci\u00f3n le falta la clase de aplicaci\u00f3n. +m.missing_code = El archivo de configuraci\u00f3n especifica que no hay recursos de c\u00f3digo. +m.invalid_digest_file = El archivo digest no es v\u00e1lido. + diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties new file mode 100644 index 0000000..3666204 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties @@ -0,0 +1,111 @@ +# +# $Id: messages.properties 485 2012-03-08 22:05:30Z ray.j.greenwell $ +# +# Getdown translation messages + +m.abort_title = Annuler l'installation? +m.abort_confirm =\u00cates-vous s\u00fbr de vouloir annuler l'installation? \ + Vous pourrez reprendre l'installation en ex\u00e9cutant l'application de nouveau. +m.abort_ok = Quitter +m.abort_cancel = Continuer l'installation + +m.detecting_proxy = D\u00e9tection automatique des r\u00e9glages proxy + +m.configure_proxy =Connexion au serveur impossible. \ +

  • Veuillez v\u00e9rifier que javaw.exe n'est bloqu\u00e9 \ + par aucun pare-feu ou antivirus. \ + Vous pouvez vous rendre sur la configuration du pare-feu windows via \ + (D\u00e9marrer -> Panneau de Configuration -> Pare-feu Windows ).
\ +

Il est \u00e9galement possible que vous soyez derri\u00e8re un proxy que l'application \ + est incapable de d\u00e9tecter automatiquement. \ + Si tel est le cas, veuillez saisir les r\u00e9glages proxy ci-dessous. + +m.proxy_extra =Si vous \u00eates certain de ne pas utiliser de proxy, il est \ + possible qu'une interruption temporaire de la connexion internet emp\u00fbche la \ + communication avec les serveurs. Dans ce cas, vous pouvez relancer \ + l'installation ult\u00e9rieurement. + +m.proxy_host = Proxy IP +m.proxy_port = Proxy port +m.proxy_username = Nom d'utilisateur +m.proxy_password = Mot de passe +m.proxy_auth_required = Identification requise +m.proxy_ok = OK +m.proxy_cancel = Annuler + +m.downloading_java = T\u00e9l\u00e9chargement en cours de la Machine Virtuelle Java +m.unpacking_java = D\u00e9compression en cours de la Machine Virtuelle Java + +m.resolving = R\u00e9solution des t\u00e9l\u00e9chargements en cours +m.downloading = T\u00e9l\u00e9chargement des donn\u00e9es en cours +m.failure = \u00c9chec du t\u00e9l\u00e9chargement: {0} + +m.checking = V\u00e9rification de la mise-\u00e0-jour en cours +m.validating = Validation en cours +m.patching = Modification en cours +m.launching = Lancement en cours + +m.patch_notes = Notes de mise-\u00e0-jour + +m.complete = Complet \u00e0 {0}% +m.remain = {0} restant + +m.updating_metadata = T\u00e9l\u00e9chargement des fichiers de contr\u00f4les en cours + +m.init_failed = Notre fichier de configuration est perdu ou corrompu. T\u00e9l\u00e9chargement \ + d'une nouvelle copie en cours ... + +m.java_download_failed = Impossible de t\u00e9l\u00e9charger automatiquement la \ + version de Java n\u00e9cessaire.\n\n\ + Veuillez vous rendre sur www.java.com et t\u00e9l\u00e9charger et installer la version \ + la plus r\u00e9cente de Java, avant d'ex\u00e9cuter l'application \u00e0 nouveau. + +m.java_unpack_failed = Impossible de d\u00e9compresser la version de \ + Java n\u00e9cessaire. Veuillez v\u00e9rifier que vous avez au moins 100 MB d'espace libre \ + sur votre disque dur puis tenter d'ex\u00e9cuter l'application \u00e0 nouveau.\n\n\ + Si le probl\u00e8me persiste, rendez vous www.java.com et t\u00e9l\u00e9chargez et \ + installez la version plus r\u00e9cente de Java puis essayez de nouveau. + +m.unable_to_repair = Impossible de t\u00e9l\u00e9charger les fichiers n\u00e9cessaires apr\u00e8s \ + cinq tentatives. Vous pouvez tenter d'ex\u00e9cuter l'application \u00e0 nouveau, mais il est \ + possible qu'une d\u00e9sinstallation / r\u00e9installation soit n\u00e9cessaire. + +m.unknown_error = Une erreur inconnue a fait \u00e9chouer le lancement de l'application. \ + Veuillez visiter\n{0} pour plus d'informations. +m.init_error = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause de l'erreur \ + suivante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations. + +m.readonly_error = Le r\u00e9pertoire d'installation de cette application: \ + \n{0}\nest en lecture seule. Veuillez installer l'application dans un r\u00e9pertoire avec \ + un acc\u00e8s en \u00e9criture. + +m.missing_resource = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause d'une \ + ressource manquante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations. + +m.insufficient_permissions_error = Vous n'avez pas accepter la signature \ + num\u00e9rique de cette application. Si vous souhaitez ex\u00e9cuter cette application, vous \ + devez accepter sa signature num\u00e9rique.\n\nAfin de le faire, vous devez quitter votre \ + navigateur, le red\u00e9marrer, retourner \u00e0 cette page puis relancer l'application. \ + Une fois la bo\u00eete de dialogue de s\u00e9curit\u00e9 affich\u00e9e, cliquez sur le bouton \ + pour accepter la signature num\u00e9rique et accorder les permissions n\u00e9cessaires au bon \ + fonctionnement de l'application. + +m.corrupt_digest_signature_error = Nous ne pouvons pas v\u00e9rifier la signature num\u00e9rique \ + de l'application.\nVeuillez v\u00e9rifier que vous lancez l'application \ndepuis \ + la bonne adresse internet. + +m.default_install_error = la section de support du site + +m.another_getdown_running = Plusieurs instances d'installation de cette \ + application sont d\u00e9j\u00e0 en cours d'ex\u00e9cution. Cette instance va s'arr\u00eater \ + afin de permettre aux autres d'aboutir. + +m.applet_stopped = L'appelet Getdown a \u00e9t\u00e9 stopp\u00e9e. + +# application/digest errors +m.missing_appbase = Le fichier de configuration ne contient pas 'appbase'. +m.invalid_version = Le fichier de configuration sp\u00e9cifie une version invalide. +m.invalid_appbase = Le fichier de configuration sp\u00e9cifie un 'appbase' invalide. +m.missing_class = Le fichier de configuration ne contient pas la classe de l'application. +m.missing_code = Le fichier de configuration ne sp\u00e9cifie aucune ressource de code. +m.invalid_digest_file = Le fichier digest est invalide. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties new file mode 100644 index 0000000..33b3260 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties @@ -0,0 +1,114 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Annullare l'installazione? +m.abort_confirm = Sei sicuro di voler annullare l'installazione? \ + Potrai riprenderla in seguito, riavviando nuovamente l'applicazione. +m.abort_ok = Chiudi +m.abort_cancel = Continua l'installazione + +m.detecting_proxy = Provo a recuperare le configurazioni del proxy + +m.configure_proxy = Impossibile collegarsi al server per \ + recuperare i dati. \ +

  • Se il Firewall di Windows o Norton Internet Security bloccano \ + javaw.exe non si possono scaricare i dati. Devi \ + permettere a javaw.exe di accedere a internet. Puoi provare \ + di nuovo, ma dovresti abilitare javaw.exe nella tua configurazione \ + del firewall ( Start -> Pannello di Controllo -> Windows Firewall ).
\ +

Il tuo computer potrebbe accedere a internet attraverso un proxy e \ + questo potrebbe non essere stato riconosciuto automaticamente. Se conosci le \ + tue impostazioni del proxy, puoi inserirle di seguito. + +m.proxy_extra = Se sei sicuro di non usare proxy \ + potrebbe essere un problema di internet o di collegamento con il server. \ + In questo caso puoi annullare e ripetere l'installazione più tardi. + +m.proxy_host = IP Proxy +m.proxy_port = Porta Proxy +m.proxy_username = Nome utente +m.proxy_password = Parola d'ordine +m.proxy_auth_required = Autenticazione richiesta +m.proxy_ok = OK +m.proxy_cancel = Annulla + +m.downloading_java = Scaricando la Java Virtual Machine +m.unpacking_java = Scompattando la Java Virtual Machine + +m.resolving = Recuperando i file da scaricare +m.downloading = Download dei dati +m.failure = Download fallito: {0} + +m.checking = Sto controllando gli aggiornamenti +m.validating = Validazione +m.patching = Applico le patch +m.launching = Avvio + +m.patch_notes = Note delle Patch +m.play_again = Avvia Nuovamente + +m.complete = {0}% completato +m.remain = {0} rimasto + +m.updating_metadata = Scarico i file di controllo + +m.init_failed = La configurazione è corrotta o mancante. Provo a \ + scaricarne una nuova copia... + +m.java_download_failed = Impossibile scaricare la versione corretta \ + di Java per il tuo computer.\n\n\ + Visita www.java.com e scarica l'ultima versione di \ + Java, poi lancia di nuovo l'applicazione. + +m.java_unpack_failed = Impossibile scompattare l'aggiornamento di \ + Java. Verifica di avere almeno 100 MB di spazio libero nel tuo \ + hard disk e prova a rilanciare l'applicazione.\n\n\ + Se l'errore persiste, vistia www.java.com, scarica e \ + installa l'ultima versione di Java e riprova. + +m.unable_to_repair = Impossibile scaricare i file necessari dopo 5 \ + tentativi. Puoi provare a rilanciare l'applicazione, ma se fallisce \ + di nuovo potresti dover reinstallarla. + +m.unknown_error = L'applicazione non è stata avviata a causa di uno strano \ + errore che non conosco. Visita\n{0} per avere informazioni \ + in merito. +m.init_error = L'applicazione non è stata avviata a causa del seguente \ + errore:\n{0}\n\nVistita\n{1} per avere \ + informazioni su come risolvere il problema. + +m.readonly_error = La directory dove l'applicazione è installata: \ + \n{0}\nè in sola lettura. Installa l'applicazione dove hai i diritti \ + di scrittura. + +m.missing_resource = L'applicazione non è stata avviata a causa di mancanza \ + di risorse:\n{0}\n\nVisita\n{1} per avere informazioni su come risolvere \ + questi problemi. + +m.insufficient_permissions_error = Non hai accettato la \ + firma digitale. Se vuoi eseguire l'applicazione devi accettare la \ + firma digitale.\n\nPer farlo, riavvia il tuo browser \ + e ritorna in questa pagina per rilanciare l'applicazione. Quando l'avviso \ + di sicurezza viene mostrato, clicca per accettare la firma digitale \ + ed eseguire l'applicazione con i privilegi necessari. + +m.corrupt_digest_signature_error = Impossibile verificare la firma digitale dell'applicazione \ + .\nControlla di aver lanciato l'applicazione dal\n\ + sito web corretto. + +m.default_install_error = la sezione di supporto del sito + +m.another_getdown_running = E' già in esecuzione un'istanza del programma. \ + Questa verrà chiusa. + +m.applet_stopped = L'applet di Getdown è stata interrotta. + +# application/digest errors +m.missing_appbase = Il tag "appbase" è mancante. +m.invalid_version = Il file di configurazione non contiene una versione valida (tag "version"). +m.invalid_appbase = Il tag "appbase" non è valido. +m.missing_class = Il file di configurazione non contiene la classe da eseguire (tag "class"). +m.missing_code = Il file di configurazione non contiene alcuna risorsa (tag "code"). +m.invalid_digest_file = Il file di digest non è valido. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties new file mode 100644 index 0000000..c344c16 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties @@ -0,0 +1,107 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f +m.abort_confirm = \u672c\u5f53\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f \ + \u5f8c\u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u305f\u969b\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u518d\u958b\u3067\u304d\u307e\u3059\u3002 +m.abort_ok = \u4e2d\u6b62 +m.abort_cancel = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u306e\u7d9a\u884c + +m.detecting_proxy = \u81ea\u52d5\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u5b9f\u884c\u4e2d + +m.configure_proxy = \u30b5\u30fc\u30d0\u306b\u63a5\u7d9a\u3067\u304d\u306a\u3044\u305f\u3081\u3001\u30b2\u30fc\u30e0\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b \ + \u5931\u6557\u3057\u307e\u3057\u305f\u3002 \ +

  • \u30a6\u30a3\u30f3\u30c9\u30a6\u30ba\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u307e\u305f\u306f\u30ce\u30fc\u30c8\u30f3\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u304c \ + javaw.exe\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b\u3088\u3046\u8a2d\u5b9a\u3057\u3066\u3042\u308b\u5834\u5408\u306f\u3001\u30b2\u30fc\u30e0\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3002 \u8a2d\u5b9a\u3092 \ + javaw.exe\u7d4c\u7531\u3067\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u3088\u3046\u306b\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \u30b2\u30fc\u30e0\u3092\u518d\u8d77\u52d5 \ + \u3057\u305f\u5f8c\u3001\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u306e\u8a2d\u5b9a\u304b\u3089javaw.exe \u3092\u524a\u9664 \ + \u3057\u3066\u304f\u3060\u3055\u3044\uff08\u30b9\u30bf\u30fc\u30c8\u2192\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d1\u30cd\u30eb\u2192\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\uff09\u3002
\ +

\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u81ea\u52d5\u691c\u51fa\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306f \ + \u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u3078\u30a2\u30af\u30bb\u30b9\u3057\u3066\u3044\u307e\u3059\u3002 \u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u8a73\u7d30\u304c \ + \u308f\u304b\u3063\u3066\u3044\u308b\u5834\u5408\u306f\u3001\u4e0b\u306b\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.proxy_extra = \u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001\u4e00\u6642\u7684\u306a \ + \u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306e\u4e0d\u5177\u5408\u306b\u3088\u308a\u3001\u30b5\u30fc\u30d0\u3068\u4ea4\u4fe1\u3067\u304d\u306a\u3044\u72b6\u614b\u306b\u3042\u308b \ + \u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002 \u305d\u306e\u5834\u5408\u306f\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u3066\u3001 \ + \u5f8c\u307b\u3069\u6539\u3081\u3066\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.proxy_host = \u30d7\u30ed\u30ad\u30b7IP +m.proxy_port = \u30d7\u30ed\u30ad\u30b7\u30dd\u30fc\u30c8 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = \u30ad\u30e3\u30f3\u30bb\u30eb + +m.downloading_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d +m.unpacking_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u89e3\u51cd\u4e2d + +m.resolving = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306e\u8a2d\u5b9a\u4e2d +m.downloading = \u30c7\u30fc\u30bf\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d +m.failure = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u5931\u6557\uff1a {0} + +m.checking = \u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u306e\u78ba\u8a8d\u4e2d +m.validating = \u8a8d\u8a3c\u4e2d +m.patching = \u4fee\u6b63\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u5b9f\u884c\u4e2d +m.launching = \u5b9f\u884c\u4e2d + +m.complete = {0}\uff05\u5b8c\u4e86 +m.remain = \u3000\u6b8b\u308a{0} + +m.updating_metadata = \u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d + +m.init_failed = \u74b0\u5883\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3001\u307e\u305f\u306f\u58ca\u308c\u3066\u3044\u307e\u3059\u3002 \u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \ + \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d\u2026 + +m.java_download_failed = \u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306b\u3001Java\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u6700\u65b0 \ + \u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u81ea\u52d5\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\n \ + www.java.com \u304b\u3089\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u624b\u52d5\u3067\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u3001 \ + \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.java_unpack_failed = Java\u306e\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u89e3\u51cd \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u30cf\u30fc\u30c9\u30c9\u30e9\u30a4\u30d6\u306e\u30e1\u30e2\u30ea\u304c100MB\u4ee5\u4e0a\u3042\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304b\u3089 \ + \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n \ + \u554f\u984c\u304c\u89e3\u6c7a\u3057\u306a\u3044\u5834\u5408\u306f\u3001www.java.com \u304b\u3089Java\u306e\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \ + \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u304b\u3089\u3001\u518d\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002 + +m.unable_to_repair = 5\u56de\u8a66\u884c\u3057\u307e\u3057\u305f\u304c\u3001\u5fc5\u8981\u306a\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9 \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u5f8c\u307b\u3069\u6539\u3081\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \ + \u518d\u5ea6\u5931\u6557\u3057\u305f\u5834\u5408\u306f\u3001\u30a2\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u5f8c\u306b\u518d\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.unknown_error = \u539f\u56e0\u4e0d\u660e\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c \ + \u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u89e3\u6c7a\u65b9\u6cd5\u3092\n{0}\u3067 \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +m.init_error = \u6b21\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \ + \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067 \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.readonly_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u305f\u30d5\u30a9\u30eb\u30c0\u306f \ + \n{0}\n\u8aad\u307f\u53d6\u308a\u5c02\u7528\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u66f8\u304d\u8fbc\u307f\u304c\u3067\u304d\u308b\u30d5\u30a9\u30eb\u30c0\u306b \ + \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.missing_resource = \u30ea\u30bd\u30fc\u30b9\u4e0d\u660e\u306e\u305f\u3081\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \ + \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067\u78ba\u8a8d \ + \u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.insufficient_permissions_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u62d2\u5426 \ + \u3055\u308c\u307e\u3057\u305f\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u5834\u5408\u306f\u3001\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u306e\u627f\u8a8d\u304c \ + \u5fc5\u8981\u3067\u3059\u3002\n\n\u627f\u8a8d\u306b\u306f\u3001\u30d6\u30e9\u30a6\u30b6\u3092\u9589\u3058\u3066\u304b\u3089\u518d\u5ea6\u958b\u304d\u3001 \ + \u672c\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3092\u518d\u8868\u793a\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u5ea6\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044 \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e \ + \u8b66\u544a\u304c\u8868\u793a\u3055\u308c\u305f\u6642\u306f\u3001\u5b9f\u884c\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u3092\u627f\u8a8d\u3057\u3001 \ + \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.corrupt_digest_signature_error = \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u8a8d\u8a3c \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\u6307\u5b9a\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u3044\u308b\u304b\n \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.default_install_error = \u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3067\u306e\u30b5\u30dd\u30fc\u30c8\u8868\u793a + +# application/digest errors +m.missing_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eappbase\u304c\u4e0d\u660e\u3067\u3059\u3002 +m.invalid_version = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306f\u7121\u52b9\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 +m.invalid_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u306aappbase\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 +m.missing_class = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30af\u30e9\u30b9\u304c\u4e0d\u660e\u3067\u3059\u3002 +m.missing_code = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u3067\u30b3\u30fc\u30c9\u30ea\u30bd\u30fc\u30b9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +m.invalid_digest_file = \u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u3067\u3059\u3002 diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties new file mode 100644 index 0000000..3f8a47f --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties @@ -0,0 +1,102 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? +m.abort_confirm = \uC815\uB9D0\uB85C \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \ + \uB098\uC911\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uC5EC \uC124\uCE58\uB97C \uC7AC\uAC1C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. +m.abort_ok = \uC911\uC9C0 +m.abort_cancel = \uACC4\uC18D\uD558\uC5EC \uC124\uCE58 + +m.detecting_proxy = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4 + +m.configure_proxy = \uAC8C\uC784 \uB370\uC774\uD130\uB97C \uBC1B\uAE30 \uC704\uD55C \uC11C\uBC84 \uC811\uC18D\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4.\ +

  • \uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD \uB610\uB294 \uB178\uD134 \uC778\uD130\uB137 \uC2DC\uD050\uB9AC\uD2F0\uAC00 javaw.exe\uC774 \uC124\uC815\uC5D0\uC11C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC744 \uACBD\uC6B0, \ + \uAC8C\uC784 \uB370\uC774\uD130\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \ + javaw.exe\uAC00 \uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD560 \uC218 \uC788\uB3C4\uB85D \uC124\uC815\uC744 \uBCC0\uACBD\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + \uAC8C\uC784\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD55C \uD6C4, \uBC29\uD654\uBCBD \uC124\uC815\uC5D0\uC11C javaw.exe\uB97C \uC0AD\uC81C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + ( \uC2DC\uC791 -> \uC81C\uC5B4\uD310 -> \uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD )
\ +

\uCEF4\uD4E8\uD130\uAC00 \uD504\uB85D\uC2DC \uC11C\uBC84\uB97C \uD1B5\uD574 \uC778\uD130\uB137\uC5D0 \uC5F0\uACB0\uB418\uC5B4 \uC788\uB2E4\uBA74, \uD504\uB85D\uC2DC \uC124\uC815\uC758 \uC790\uB3D9 \uAD6C\uC131\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC73C\uBBC0\uB85C, \ + \uC0AC\uC6A9\uD558\uB294 \uD504\uB85D\uC2DC \uC124\uC815\uC744 \uC54C\uACE0 \uC788\uC744 \uACBD\uC6B0 \uC544\uB798\uC5D0 \uC785\uB825\uD558\uC5EC \uC8FC\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.proxy_extra = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4 + +m.proxy_host = \uD504\uB85D\uC2DC IP +m.proxy_port = \uD504\uB85D\uC2DC \uD3EC\uD2B8 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = \uCDE8\uC18C + +m.downloading_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uB2E4\uC6B4\uB85C\uB4DC \uC911 +m.unpacking_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uC555\uCD95\uC744 \uD574\uC81C\uD558\uB294 \uC911 + +m.resolving = \uB2E4\uC6B4\uB85C\uB4DC \uBD84\uC11D \uC911 +m.downloading = \uB370\uC774\uD130 \uB2E4\uC6B4\uB85C\uB4DC \uC911 +m.failure = \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328: {0} + +m.checking = \uC5C5\uB370\uC774\uD2B8 \uCCB4\uD06C +m.validating = \uC720\uD6A8\uC131 \uAC80\uC0AC \uC911 +m.patching = \uD328\uCE58 \uC911 +m.launching = \uC2E4\uD589 \uC911 + +m.patch_notes = \uD328\uCE58 \uB178\uD2B8 +m.play_again = \uB2E4\uC2DC \uC2E4\uD589 + +m.complete = {0}% \uC644\uB8CC +m.remain = {0} \uB0A8\uC74C + +m.updating_metadata = \uCEE8\uD2B8\uB864 \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911 + +m.init_failed = \uC124\uC815 \uD30C\uC77C\uC774 \uB204\uB77D\uB418\uC5C8\uAC70\uB098 \uBCC0\uD615\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \ + \uC0C8\uB85C\uC6B4 \uBCF5\uC0AC\uBCF8\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911\uC785\uB2C8\uB2E4... + +m.java_download_failed = \uC774 \uCEF4\uD4E8\uD130\uC5D0 \uD544\uC694\uD55C \uC0C8\uB85C\uC6B4 \uBC84\uC804\uC758 \uC790\uBC14\uB97C \uC790\uB3D9\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n\n\ + \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624. + +m.java_unpack_failed = \uC5C5\uB370\uC774\uD2B8\uB41C \uBC84\uC804\uC758 \uC790\uBC14\uC758 \uC555\uCD95\uC744 \uD480 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \ + \uD558\uB4DC\uB4DC\uB77C\uC774\uBE0C\uC5D0 \uCD5C\uC18C\uD55C 100MB\uC758 \uC6A9\uB7C9\uC744 \uD655\uBCF4\uD55C \uC774\uD6C4, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624.\n\n\ + \uB9CC\uC57D \uBB38\uC81C\uAC00 \uD574\uACB0\uB418\uC9C0 \uC54A\uB294\uB2E4\uBA74, \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624. + +m.unable_to_repair = \uB2E4\uC12F\uBC88\uC758 \uC2DC\uB3C4\uC5D0\uB3C4 \uD544\uC694\uD55C \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD574\uBCF4\uC2DC\uACE0, \uADF8\uB798\uB3C4 \uB2E4\uC6B4\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD55C\uB2E4\uBA74, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC81C\uAC70\uD55C \uD6C4, \uB2E4\uC2DC \uC2E4\uD589\uD574\uBCF4\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.unknown_error = \uBCF5\uAD6C\uB420 \uC218 \uC5C6\uB294 \uC624\uB958\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \ + \n{0}\uC5D0 \uB300\uD55C \uBCF5\uAD6C \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.init_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC544\uB798\uC640 \uAC19\uC740 \uC5D0\uB7EC\uB85C \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC5D0\uB7EC:\ + \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.readonly_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC124\uCE58\uB41C \uB514\uB809\uD1A0\uB9AC: \ + \n{0}\n\uAC00 \uC77D\uAE30 \uC804\uC6A9\uC785\uB2C8\uB2E4. \uC77D\uAE30 \uAD8C\uD55C\uC774 \uC2B9\uC778\uB41C \uB809\uD1A0\uB9AC\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC124\uCE58\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.missing_resource = \uB9AC\uC18C\uC2A4\uC758 \uC190\uC2E4\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. : \ + \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.insufficient_permissions_error = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uAE30 \uC704\uD574\uC11C \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + \n\n\uADF8\uB9AC\uACE0 \uB098\uC11C \uC6F9 \uBE0C\uB77C\uC6B0\uC800\uB97C \uB2EB\uACE0 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC5EC \uC6F9\uD398\uC774\uC9C0\uB85C \uB3CC\uC544\uC640 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC7AC\uC2DC\uC791\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. \ + \uBCF4\uC548\uC5D0 \uB300\uD55C \uB300\uD654\uC0C1\uC790\uAC00 \uBCF4\uC774\uBA74, \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC5D0 \uB300\uD55C \uD655\uC778 \uBC84\uD2BC\uC744 \uD074\uB9AD\uD558\uACE0, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uAE30 \uC704\uD55C \ + \uAD8C\uD55C\uC744 \uBD80\uC5EC\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.corrupt_digest_signature_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n \ + \uC62C\uBC14\uB978 \uC6F9\uC0AC\uC774\uD2B8\uC5D0\uC11C \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uACE0 \uC788\uB294 \uC9C0 \uD655\uC778\uBC14\uB78D\uB2C8\uB2E4. + +m.default_install_error = \uC6F9\uC0AC\uC774\uD2B8\uC758 \uC9C0\uC6D0 \uBA54\uB274(support section)\uB97C \uD655\uC778\uD558\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.another_getdown_running = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uC778\uC2A4\uD1A8\uB7EC\uC758 \uB2E4\uC911 \uC778\uC2A4\uD134\uC2A4\uAC00 \uC2E4\uD589\uC911\uC785\uB2C8\uB2E4. \ + \uD558\uB098\uAC00 \uC644\uB8CC\uB420 \uB54C\uAE4C\uC9C0 \uC911\uB2E8\uB429\uB2C8\uB2E4. + +m.applet_stopped = Getdown \uC560\uD50C\uB9BF \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. + +# application/digest errors +m.missing_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 'appbase' \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. +m.invalid_version = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C \uBC84\uC804\uC774 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. +m.invalid_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C 'appbase'\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. +m.missing_class = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uD074\uB798\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. +m.missing_code = \uC124\uC815 \uD30C\uC77C\uC5D0 \uB9AC\uC18C\uC2A4\uC5D0 \uB300\uD55C \uCF54\uB4DC\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +m.invalid_digest_file = \uB2E4\uC774\uC81C\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties new file mode 100644 index 0000000..47db91c --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties @@ -0,0 +1,118 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Cancelar a instala\u00E7\u00E3o? +m.abort_confirm = Tem certeza que deseja cancelar a instala\u00E7\u00E3o? \ + Voc\u00EA pode continuar a instala\u00E7\u00E3o mais tarde, \ + basta executar a aplica\u00E7\u00E3o novamente. +m.abort_ok = Sair +m.abort_cancel = Continuar a instala\u00E7\u00E3o + +m.detecting_proxy = Tentando detectar automaticamente as configura\u00E7\u00F5es de proxy + +m.configure_proxy = N\u00E3o foi poss\u00EDvel conectar aos nossos servidores para \ + fazer o download dos dados. \ +

  • Se o Firewall do Windows ou o Norton Internet Security est\u00E1 configurado \ + para bloquear o programa javaw.exe n\u00E3o ser\u00E1 poss\u00EDvel realizar \ + o download. Voc\u00EA ter\u00E1 que permitir que o programa javaw.exe acesse \ + a internet. Voc\u00EA pode tentar executar o programa novamente, mas voc\u00EA precisa \ + remover o programa javaw.exe das configura\u00E7\u00F5es do firewall (Iniciar -> Painel \ + de controle -> Firewall do Windows).
\ +

Seu computador pode estar acessando a internet atrav\u00E9s de um proxy e n\u00E3o foi \ + capaz de detectar automaticamente as configura\u00E7\u00F5es de proxy. \ + Voc\u00EA pode informar esses dados abaixo. + +m.proxy_extra = Se voc\u00EA tem certeza que n\u00E3o usa um proxy, ent\u00E3o pode ser \ + que exista um problema tempor\u00E1rio que est\u00E1 impedindo a comunica\u00E7\u00E3o \ + com os nossos servidores. Neste caso voc\u00EA pode cancelar e tentar instalar novamente \ + mais tarde. + +m.proxy_host = IP do Proxy +m.proxy_port = Porta do Proxy +m.proxy_username = Nome de usu\u00e1rio +m.proxy_password = Senha +m.proxy_auth_required = Autentifica\u00e7\u00e3o requerida +m.proxy_ok = OK +m.proxy_cancel = Cancelar + +m.downloading_java = Fazendo o download da m\u00E1quina virtual Java +m.unpacking_java = Descompactando a m\u00E1quina virtual Java + +m.resolving = Resolvendo downloads +m.downloading = Transferindo dados +m.failure = Download falhou: {0} + +m.checking = Verificando atualiza\u00E7\u00F5es +m.validating = Validando +m.patching = Atualizando +m.launching = Executando + +m.patch_notes = Corrigir notas +m.play_again = Jogar de novo + +m.complete = {0}% completo +m.remain = {0} Permanecer + +m.updating_metadata = Transferindo arquivos de controle + +m.init_failed = Nosso arquivo de configura\u00E7\u00E3o est\u00E1 ausente ou corrompido. Tente \ + baixar uma nova c\u00F3pia... + +m.java_download_failed = N\u00E3o conseguimos baixar automaticamente a\ + vers\u00E3o necess\u00E1ria do Java para o seu computador.\n\n\ + Por favor, acesse www.java.com, baixe e instale a \u00FAltima vers\u00E3o do \ + Java, em seguida, tente executar o aplicativo novamente. + +m.java_unpack_failed = N\u00E3o conseguimos descompactar uma vers\u00E3o atualizada do \ + Java. Por favor, certifique-se de ter pelo menos 100 MB de espa\u00E7o livre em seu \ + disco r\u00EDgido e tente executar o aplicativo novamente. \n\n\ + Se isso n\u00E3o resolver o problema, acesse www.java.com,baixe e \ + instale a \u00FAltima vers\u00E3o do Java e tente novamente. + +m.unable_to_repair = N\u00E3o conseguimos baixar os arquivos necess\u00E1rios depois de \ + cinco tentativas. Voc\u00EA pode tentar executar o aplicativo novamente, mas se ele \ + falhar pode ser necess\u00E1rio desinstalar e reinstalar. + +m.unknown_error = A aplica\u00E7\u00E3o falhou ao iniciar devido a algum erro estranho \ + do qual n\u00E3o conseguimos recuperar. Por favor, visite \n{0} para obter \ + informa\u00E7\u00F5es sobre como recuperar. +m.init_error = A aplica\u00E7\u00E3o falhou ao iniciar devido ao seguinte \ + erro:\n{0}\n\nPor favor visite \n{1} para \ + informa\u00E7\u00F5es sobre como lidar com esse problema. + +m.readonly_error =O diret\u00F3rio no qual este aplicativo est\u00E1 instalado: \ + \n{0}\n \u00E9 somente leitura. Por favor, instale o aplicativo em um diret\u00F3rio onde \ + voc\u00EA tem acesso de grava\u00E7\u00E3o. + +m.missing_resource = A aplica\u00E7\u00E3o falhou ao iniciar devido a uma falta \ + de recurso:\n{0}\n\n Por favor, visite\n{1} para obter informa\u00E7\u00F5es sobre \ + como lidar com tal problema. + +m.insufficient_permissions_error = Voc\u00EA n\u00E3o aceitou a assinatura digital \ + do aplicativo. Se voc\u00EA quiser executar o aplicativo, voc\u00EA ter\u00E1 que aceitar \ + a assinatura digital. \n\nPara fazer isso, voc\u00EA ter\u00E1 que sair do seu navegador, \ + reinici\u00E1-lo, e retornar a esta p\u00E1gina web para executar a aplica\u00E7\u00E3o. \ + Quando o di\u00E1logo de seguran\u00E7a aparecer, clique no bot\u00E3o para aceitar a \ + assinatura digital e conceder a este aplicativo os privil\u00E9gios necess\u00E1rios \ + para executar. + +m.corrupt_digest_signature_error = N\u00E3o conseguimos verificar a assinatura digital \ + do aplicativo.\nPor favor, verifique se voc\u00EA est\u00E1 utilizando o aplicativo \nde um \ + site correto. + +m.default_install_error = a se\u00E7\u00E3o de suporte do site + +m.another_getdown_running = V\u00E1rias inst\u00E2ncias desta aplica\u00E7\u00E3o \ + est\u00E3o em execu\u00E7\u00E3o. Esta ir\u00E1 parar e deixar outra completar suas atividades. + +m.applet_stopped = Foi solicitado ao miniaplicativo GetDow que parasse de trabalhar. + +# application/digest errors +m.missing_appbase = O arquivo de configura\u00E7\u00E3o n\u00E3o possui o 'AppBase'. +m.invalid_version = O arquivo de configura\u00E7\u00E3o especifica uma vers\u00E3o inv\u00E1lida. +m.invalid_appbase = O arquivo de configura\u00E7\u00E3o especifica um 'AppBase' inv\u00E1lido. +m.missing_class = O arquivo de configura\u00E7\u00E3o n\u00E3o possui a classe de aplicativo. +m.missing_code = O arquivo de configura\u00E7\u00E3o n\u00E3o especifica um recurso de c\u00F3digo. +m.invalid_digest_file = O arquivo digest \u00E9 inv\u00E1lido. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties new file mode 100644 index 0000000..2c27543 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties @@ -0,0 +1,61 @@ +# +# $Id$ +# +# Getdown translation messages + +m.detecting_proxy = \u641c\u5bfb\u4ee3\u7406\u670d\u52a1\u5668 + +m.configure_proxy = \u6211\u4eec\u65e0\u6cd5\u8fde\u63a5\u5230\u670d\u52a1\u5668\u4e0b\u8f7d\u6e38\u620f\u6570\u636e\u3002\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e \ + \u60a8\u7684\u8ba1\u7b97\u673a\u662f\u901a\u8fc7\u4ee3\u7406\u670d\u52a1\u5668\u8fde\u63a5\u4e92\u8054\u7f51\u7684\uff0c\u5e76\u4e14\u6211\u4eec\u65e0\u6cd5\u81ea\u52a8\u83b7\u5f97\u4ee3\u7406\u670d\u52a1\u5668\u7684 \ + \u8bbe\u7f6e\u3002\u5982\u679c\u60a8\u77e5\u9053\u60a8\u4ee3\u7406\u670d\u52a1\u5668\u7684\u8bbe\u7f6e\uff0c\u60a8\u53ef\u4ee5\u5728\u4e0b\u9762\u8f93\u5165\u3002 + +m.proxy_extra = \u5982\u679c\u60a8\u786e\u5b9a\u60a8\u6ca1\u6709\u4f7f\u7528\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e\u6682\u65f6\u65e0\u6cd5 \ + \u8fde\u63a5\u5230\u4e92\u8054\u7f51\uff0c\u5bfc\u81f4\u65e0\u6cd5\u548c\u670d\u52a1\u5668\u901a\u8baf\u3002\u8fd9\u79cd\u60c5\u51b5\uff0c\u60a8\u53ef\u4ee5\u53d6\u6d88\uff0c\u7a0d\u5019\u518d\u91cd\u65b0\u5b89\u88c5\u3002

\ + \u5982\u679c\u60a8\u65e0\u6cd5\u786e\u5b9a\u60a8\u662f\u5426\u4f7f\u7528\u4e86\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8bf7\u8bbf\u95ee\u6211\u4eec\u7f51\u7ad9\u4e2d\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c \ + \u4e86\u89e3\u5982\u4f55\u68c0\u6d4b\u60a8\u7684\u4ee3\u7406\u670d\u52a1\u5668\u8bbe\u7f6e\u3002 + +m.proxy_host = \u4ee3\u7406\u670d\u52a1\u5668\u7684IP\u5730\u5740 +m.proxy_port = \u4ee3\u7406\u670d\u52a1\u5668\u7684\u7aef\u53e3\u53f7 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = \u786e\u5b9a +m.proxy_cancel = \u53d6\u6d88 + +m.resolving = \u5206\u6790\u9700\u4e0b\u8f7d\u5185\u5bb9 +m.downloading = \u4e0b\u8f7d\u6570\u636e +m.failure = \u4e0b\u8f7d\u5931\u8d25: {0} + +m.checking = \u68c0\u67e5\u66f4\u65b0\u5185\u5bb9 +m.validating = \u786e\u8ba4 +m.patching = \u5347\u7ea7 +m.launching = \u542f\u52a8 + +m.complete = {0}% \u5b8c\u6210 +m.remain = {0} \u5269\u4f59\u65f6\u95f4 + +m.updating_metadata = \u4e0b\u8f7d\u63a7\u5236\u6587\u4ef6 + +m.init_failed = \u65e0\u6cd5\u627e\u5230\u914d\u7f6e\u6587\u4ef6\u6216\u5df2\u635f\u574f\u3002\u5c1d\u8bd5\u91cd\u65b0\u4e0b\u8f7d... + +m.unable_to_repair = \u7ecf\u8fc75\u6b21\u5c1d\u8bd5\uff0c\u4f9d\u7136\u65e0\u6cd5\u4e0b\u8f7d\u6240\u9700\u7684\u6587\u4ef6\u3002\ +\u60a8\u53ef\u4ee5\u91cd\u65b0\u8fd0\u884c\u7a0b\u5e8f\uff0c\u4f46\u662f\u5982\u679c\u4f9d\u7136\u5931\u8d25\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u53cd\u5b89\u88c5\u5e76\u91cd\u65b0\u5b89\u88c5\u3002 + + +m.unknown_error = \u7531\u4e8e\u4e00\u4e9b\u65e0\u6cd5\u56de\u590d\u7684\u4e25\u91cd\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\u3002\ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u89e3\u51b3\u95ee\u9898\u3002 + +m.init_error = \u7531\u4e8e\u4e0b\u5217\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u9519\u8bef\u3002 + + +m.missing_resource = \u7531\u4e8e\u65e0\u6cd5\u627e\u5230\u4e0b\u5217\u8d44\u6e90\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u95ee\u9898\u3002 + +# application/digest errors +m.missing_appbase = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230 'appbase'\u3002 +m.invalid_version = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684\u7248\u672c\u3002 +m.invalid_appbase = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684 'appbase'\u3002 +m.missing_class = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u7a0b\u5e8f\u6587\u4ef6\u3002 +m.missing_code = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u6307\u5b9a\u7684\u8d44\u6e90\u3002 +m.invalid_digest_file = \u65e0\u6548\u7684\u914d\u7f6e\u6587\u4ef6\u3002 diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages.properties new file mode 100644 index 0000000..19b2999 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages.properties @@ -0,0 +1,110 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Abort installation? +m.abort_confirm = Are you sure you want to stop installation? \ + You can resume at a later time by running the application again. +m.abort_ok = Quit +m.abort_cancel = Continue installation + +m.detecting_proxy = Trying to auto-detect proxy settings + +m.configure_proxy = We were unable to connect to the application server to download data. \ +

Please make sure that no virus scanner or firewall is blocking network communicaton with \ + the server. \ +

Your computer may access the Internet through a proxy and we were unable to automatically \ + detect your proxy settings. If you know your proxy settings, you can enter them below. + +m.proxy_extra = If you are sure that you don't use a proxy then \ + perhaps there is a temporary Internet outage that is preventing us from \ + communicating with the servers. In this case, you can cancel and try \ + installing again later. + +m.proxy_host = Proxy IP +m.proxy_port = Proxy port +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = Cancel + +m.downloading_java = Downloading Java Virtual Machine +m.unpacking_java = Unpacking Java Virtual Machine + +m.resolving = Resolving downloads +m.downloading = Downloading data +m.failure = Download failed: {0} + +m.checking = Checking for update +m.validating = Validating +m.patching = Patching +m.launching = Launching + +m.patch_notes = Patch Notes +m.play_again = Play Again + +m.complete = {0}% complete +m.remain = {0} remaining + +m.updating_metadata = Downloading control files + +m.init_failed = Our configuration file is missing or corrupt. Attempting \ + to download a new copy... + +m.java_download_failed = We were unable to automatically download the \ + necessary version of Java for your computer.\n\n\ + Please go to www.java.com and download the latest version of \ + Java, then try running the application again. + +m.java_unpack_failed = We were unable to unpack an updated version of \ + Java. Please make sure you have at least 100 MB of free space on your \ + harddrive and try running the application again.\n\n\ + If that does not solve the problem, go to www.java.com and download and \ + install the latest version of Java and try again. + +m.unable_to_repair = We were unable to download the necessary files after \ + five attempts. You can try running the application again, but if it \ + fails you may need to uninstall and reinstall. + +m.unknown_error = The application has failed to launch due to some strange \ + error from which we could not recover. Please visit\n{0} for information on \ + how to recover. +m.init_error = The application has failed to launch due to the following \ + error:\n{0}\n\nPlease visit\n{1} for \ + information on how to handle such problems. + +m.readonly_error = The directory in which this application is installed: \ + \n{0}\nis read-only. Please install the application into a directory where \ + you have write access. + +m.missing_resource = The application has failed to launch due to a missing \ + resource:\n{0}\n\nPlease visit\n{1} for information on how to handle such \ + problems. + +m.insufficient_permissions_error = You did not accept this application's \ + digital signature. If you want to run the application, you will need to accept \ + its digital signature.\n\nTo do so, you will need to quit your web browser, \ + restart it, and return to this web page to relaunch the application. When the \ + security dialog is shown, click the button to accept the digital signature \ + and grant this application the privileges it needs to run. + +m.corrupt_digest_signature_error = We couldn't verify the application's digital \ + signature.\nPlease check that you are launching the application from\nthe \ + correct website. + +m.default_install_error = the support section of the website + +m.another_getdown_running = Multiple instances of this application's \ + installer are running. This one will stop and let another complete. + +m.applet_stopped = Getdown's applet was told to stop working. + +# application/digest errors +m.missing_appbase = The configuration file is missing the 'appbase'. +m.invalid_version = The configuration file specifies an invalid version. +m.invalid_appbase = The configuration file specifies an invalid 'appbase'. +m.missing_class = The configuration file is missing the application class. +m.missing_code = The configuration file specifies no code resources. +m.invalid_digest_file = The digest file is invalid. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_de.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_de.properties new file mode 100644 index 0000000..8e36835 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_de.properties @@ -0,0 +1,116 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Installation abbrechen? +m.abort_confirm = Bist du sicher, dass du die Installation abbrechen \ +m\u00f6chtest? \ + Du kannst sp\u00e4ter fortfahren, indem du die Anwendung erneut \ +ausf\u00fchrst. +m.abort_ok = Beenden +m.abort_cancel = Installation fortsetzen + +m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln + +m.configure_proxy = Es konnte keine Verbindung zum Applikations-Server aufgebaut werden. \ +

Bitte kontrollieren Sie die Proxyeinstellungen und stellen Sie sicher, dass keine lokal oder \ + im Netzwerk betriebene Sicherheitsanwendung (Virenscanner, Firewall, etc.) die Kommunikation \ + mit dem Server blockiert.
\ + Wenn kein Proxy verwendet werden soll, l\u00f6schen Sie bitte alle Eintr\u00e4ge in den unten \ + stehenden Feldern und klicken sie auf OK. + +m.proxy_extra = Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \ + an Ihren Administrator. + +m.proxy_host = Proxy-Adresse +m.proxy_port = Proxy-Port +m.proxy_username = Benutzername +m.proxy_password = Passwort +m.proxy_auth_required = Authentisierung erforderlich +m.proxy_ok = OK +m.proxy_cancel = Abbrechen + +m.downloading_java = Lade Java Virtual Machine herunter +m.unpacking_java = Entpacke Java Virtual Machine + +m.resolving = Bereite Download vor +m.downloading = Lade Daten herunter +m.failure = Download fehlgeschlagen: {0} + +m.checking = Suche nach Updates +m.validating = Validiere Download +m.patching = Patche +m.launching = Starte + +m.patch_notes = Patchnotes + +m.complete = {0}% abgeschlossen +m.remain = {0} \u00fcbrig + +m.updating_metadata = Lade Steuerungsdateien herunter + +m.init_failed = Unsere Konfigurationsdatei fehlt oder ist besch\u00e4digt. \ +Versuche, eine neue Kopie herunterzuladen... + +m.java_download_failed = Wir konnten die notwendige Javaversion f\u00fcr deinen \ +Computer nicht automatisch herunterladen. \n\n \ +Bitte auf www.java.com die aktuelle Javaversion herunterladen und dann die \ +Anwendung erneut starten. + +m.java_unpack_failed = Wir konnten die aktualisierte Javaversion nicht \ +entpacken. Bitte stelle sicher, dass wenigstens 100MB Platz auf der \ +Festplatte frei sind und versuche dann die Anwendung erneut zu \ +starten.\n\n\ \ +Falls das das Problem nicht beseitigt, bitte auf www.java.com die aktuelle \ +Javaversion herunterladen und installieren und dann erneut versuchen. + +m.unable_to_repair = Wir konnten die notwendigen Dateien nach 5 Versuchen \ +nicht herunterladen. Du kannst versuchen, die Anwendung erneut zu starten, \ +aber wenn dies erneut fehlschl\u00e4gt, musst du die Anwendung deinstallieren \ +und erneut installieren. + +m.unknown_error = Die Anwendung konnte wegen eines unbekannten Fehlers \ +nicht gestartet werden. Bitte auf \n{0} weiterlesen. + +m.init_error = Die Anwendung konnte wegen folgendem Fehler nicht gestartet \ +werden:\n{0}\n\n Bitte auf \n{1} weiterlesen, um zu erfahren, wie bei \ +solchen Problemen vorzugehen ist. + +m.readonly_error = Das Verzeichnis, in dem die Anwendung installiert ist: \ + \n{0}\nist nicht schreibberechtigt. Bitte in ein Verzeichnis mit \ +Schreibzugriff installieren. + +m.missing_resource = Die Anwendung konnte nicht gestartet werden, da die \ +folgende Quelle nicht gefunden wurde:\n{0}\n\n\ Bitte auf \n{1} \ +weiterlesen, um zu erfahren, wie bei solchen Problemen vorzugehen ist. + +m.insufficient_permissions_error = Du hast die digitale Signatur dieser \ +Anwendung nicht akzeptiert. Falls du diese Anwendung benutzen willst, \ +musst du ihre digitale Signatur akzeptieren. \n\Um das zu tun, musst du \ +deinen Browser beenden, neu starten und erneut die Anwendung von dieser \ +Webseite aus starten. Wenn die Sicherheitsabfrage erscheint, bitte die \ +digitale Signatur akzeptieren, um der Anwendung die n\u00f6tigen Rechte zu \ +geben, die sie braucht, um zu laufen. + +m.corrupt_digest_signature_error = Wir konnten die digitale Signatur \ +dieser Anwendung nicht \u00fcberpr\u00fcfen.\nBitte \u00fcberpr\u00fcfe, ob du die Anwendung \ +von der richtigen Webseite aus startest. + +m.default_install_error = der Support-Webseite + +m.another_getdown_running = Diese Installationsanwendung l\u00e4uft in mehreren \ +Instanzen. Diese Instanz wird sich beenden und eine andere Instanz den \ +Vorgang erledigen lassen. + +m.applet_stopped = Die Anwendung wurde beendet. + + +# application/digest errors +m.missing_appbase = In der Konfigurationsdatei fehlt die 'appbase'. +m.invalid_version = In der Konfigurationsdatei steht die falsche Version. +m.invalid_appbase = In der Konfigurationsdatei steht die falsche 'appbase'. +m.missing_class = In der Konfigurationsdatei fehlt die Anwendungsklasse. +m.missing_code = Die Konfigurationsdatei enth\u00e4lt keine Codequellen. +m.invalid_digest_file = Die Hashwertedatei ist ung\u00fcltig. + diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_es.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_es.properties new file mode 100644 index 0000000..609b025 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_es.properties @@ -0,0 +1,115 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \u00bfCancelar la instalaci\u00f3n? +m.abort_confirm = \u00bfEst\u00e1s seguro de querer cancelar la instalaci\u00f3n? \ + Puedes continuarla despu\u00e9s si corres de nuevo la aplicaci\u00f3n. +m.abort_ok = Cancelar +m.abort_cancel = Continuar la instalaci\u00f3n + +m.detecting_proxy = Detectando autom\u00e1ticamente la configuraci\u00f3n proxy + +m.configure_proxy = No ha sido posible conectar con nuestros servidores para \ + descargar los datos del juego. \ +

  • Si el cortafuegos de Windows o Norton Internet Security tiene instrucciones \ + de bloquear javaw.exe no podemos descargar el juego. Necesitar\u00e1s \ + permitir que javaw.exe tenga acceso al Internet. Puedes intentar \ + correr el juego de nuevo, pero es posible que debas dar permisos a javaw.exe en la \ + configuraci\u00f3n de tu cortafuegos ( Inicio -> Panel de control -> Firewall de Windows ).
\ +

Es posible que tu computadora tenga acceso al Internet por medio de un proxy por lo que \ + no ha sido posible detectar autom\u00e1ticamente tu configuraci\u00f3n. Si conoces tu \ + configuraci\u00f3n proxy, puedes anotarla abajo. + +m.proxy_extra = Si est\u00e1s seguro de que no tienes un proxy entonces \ + tal vez exista un falla temporal en el Internet que est\u00e1 evitando que podamos \ + comunicarnos con los servidores. En este caso, puedes cancelar e intentar \ + instalarla de nuevo m\u00e1s tarde. + +m.proxy_host = IP proxy +m.proxy_port = Puerto proxy +m.proxy_username = Nombre de usuario +m.proxy_password = Contrase\u00f1a +m.proxy_auth_required = Autenticacion requerida +m.proxy_ok = OK +m.proxy_cancel = Cancelar + +m.downloading_java = Descargando Java Virtual Machine +m.unpacking_java = Desempacando Java Virtual Machine + +m.resolving = Resolviendo descarga +m.downloading = Descargando datos +m.failure = Descarga fallida: {0} + +m.checking = Buscando actualizaciones +m.validating = Validando +m.patching = Parchando +m.launching = Lanzando + +m.patch_notes = Notas del parche + +m.complete = {0}% completado +m.remain = {0} restante + +m.updating_metadata = Descargando los archivos de control + +m.init_failed = Un archivo de configuraci\u00f3n est\u00e1 faltante o est\u00e1 corrupto. Intentando \ + descargar una nueva copia... + +m.java_download_failed = No ha sido posible descargar autom\u00e1ticamente la \ + versi\u00f3n de Java necesaria para tu computadora.\n\n\ + Por favor ve a www.java.com y descarga la \u00faltima versi\u00f3n de \ + Java, despu\u00e9s intenta correr de nuevo la aplicaci\u00f3n. + +m.java_unpack_failed = No ha sido posible desempacar una versi\u00f3n actualizada de \ + Java. Por favor aseg\u00farate de tener al menos 100 MB de espacio libre en tu \ + disco duro e intenta correr de nuevo la aplicaci\u00f3n.\n\n\ + Si eso no soluciona el problema, ve a www.java.com y descarga e \ + instala la \u00faltima versi\u00f3n de Java e intenta de nuevo. + +m.unable_to_repair = No ha sido posible descargar los archivos necesarios despu\u00e9s de \ + cinco intentos. Puedes intentar correr de nuevo la aplicaci\u00f3n, pero si falla \ + de nuevo podr\u00edas necesitar desinstalar y reinstalar. + +m.unknown_error = La aplicaci\u00f3n no ha podido iniciar debido a un extra\u00f1o \ + error del que no se pudo recobrar. Por favor visita\n{0} para ver informaci\u00f3n acerca \ + de como recuperarla. +m.init_error = La aplicaci\u00f3n no ha podido iniciar debido al siguiente \ + error:\n{0}\n\nPor favor visita\n{1} para \ + ver informaci\u00f3n acerca de como manejar ese tipo de problemas. + +m.readonly_error = El directorio en el que esta aplicaci\u00f3n est\u00e1 instalada: \ + \n{0}\nes solo lectura. Por favor instala la aplicaci\u00f3n en un directorio en el cual \ + tengas acceso de escritura. + +m.missing_resource = La aplicaci\u00f3n no ha podido iniciar debido a un recurso \ + faltante:\n{0}\n\nPor favor visita\n{1} para informaci\u00f3n acerca de como solucionar \ + estos problemas. + +m.insufficient_permissions_error = No aceptaste la firma digital de \ + esta aplicaci\u00f3n. Si quieres correr la aplicaci\u00f3n, necesitas aceptar \ + su firma digital.\n\nPara hacerlo, necesitas cerrar tu navegador, \ + reiniciarlo, y regresar a esta p\u00e1gina web para reiniciar la aplicaci\u00f3n. Cuando se muestre \ + el di\u00e1logo de seguridad, haz clic en el bot\u00f3n para aceptar la firmar digital \ + y otorgar a esta aplicaci\u00f3n los privilegios que necesita para correr. + +m.corrupt_digest_signature_error = No pudimos verificar la firma digital \ + de la aplicaci\u00f3n.\nPor favor revisa que est\u00e9s lanzando la aplicaci\u00f3n desde\nel \ + sitio web correcto. + +m.default_install_error = la secci\u00f3n de asistencia de este sitio web + +m.another_getdown_running = Est\u00e1n corriendo m\u00faltiples instancias de \ + este instalador. Este se detendr\u00e1 para permitir que otra contin\u00fae. + +m.applet_stopped = Se le dijo al applet de Getdown que dejara de trabajar. + +# application/digest errors +m.missing_appbase = Al archivo de configuraci\u00f3n le falta el 'appbase'. +m.invalid_version = El archivo de configuraci\u00f3n especifica una versi\u00f3n no v\u00e1lida. +m.invalid_appbase = El archivo de configuraci\u00f3n especifica un 'appbase' no v\u00e1lido. +m.missing_class = Al archivo de configuraci\u00f3n le falta la clase de aplicaci\u00f3n. +m.missing_code = El archivo de configuraci\u00f3n especifica que no hay recursos de c\u00f3digo. +m.invalid_digest_file = El archivo digest no es v\u00e1lido. + diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_fr.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_fr.properties new file mode 100644 index 0000000..3666204 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_fr.properties @@ -0,0 +1,111 @@ +# +# $Id: messages.properties 485 2012-03-08 22:05:30Z ray.j.greenwell $ +# +# Getdown translation messages + +m.abort_title = Annuler l'installation? +m.abort_confirm =\u00cates-vous s\u00fbr de vouloir annuler l'installation? \ + Vous pourrez reprendre l'installation en ex\u00e9cutant l'application de nouveau. +m.abort_ok = Quitter +m.abort_cancel = Continuer l'installation + +m.detecting_proxy = D\u00e9tection automatique des r\u00e9glages proxy + +m.configure_proxy =Connexion au serveur impossible. \ +

  • Veuillez v\u00e9rifier que javaw.exe n'est bloqu\u00e9 \ + par aucun pare-feu ou antivirus. \ + Vous pouvez vous rendre sur la configuration du pare-feu windows via \ + (D\u00e9marrer -> Panneau de Configuration -> Pare-feu Windows ).
\ +

Il est \u00e9galement possible que vous soyez derri\u00e8re un proxy que l'application \ + est incapable de d\u00e9tecter automatiquement. \ + Si tel est le cas, veuillez saisir les r\u00e9glages proxy ci-dessous. + +m.proxy_extra =Si vous \u00eates certain de ne pas utiliser de proxy, il est \ + possible qu'une interruption temporaire de la connexion internet emp\u00fbche la \ + communication avec les serveurs. Dans ce cas, vous pouvez relancer \ + l'installation ult\u00e9rieurement. + +m.proxy_host = Proxy IP +m.proxy_port = Proxy port +m.proxy_username = Nom d'utilisateur +m.proxy_password = Mot de passe +m.proxy_auth_required = Identification requise +m.proxy_ok = OK +m.proxy_cancel = Annuler + +m.downloading_java = T\u00e9l\u00e9chargement en cours de la Machine Virtuelle Java +m.unpacking_java = D\u00e9compression en cours de la Machine Virtuelle Java + +m.resolving = R\u00e9solution des t\u00e9l\u00e9chargements en cours +m.downloading = T\u00e9l\u00e9chargement des donn\u00e9es en cours +m.failure = \u00c9chec du t\u00e9l\u00e9chargement: {0} + +m.checking = V\u00e9rification de la mise-\u00e0-jour en cours +m.validating = Validation en cours +m.patching = Modification en cours +m.launching = Lancement en cours + +m.patch_notes = Notes de mise-\u00e0-jour + +m.complete = Complet \u00e0 {0}% +m.remain = {0} restant + +m.updating_metadata = T\u00e9l\u00e9chargement des fichiers de contr\u00f4les en cours + +m.init_failed = Notre fichier de configuration est perdu ou corrompu. T\u00e9l\u00e9chargement \ + d'une nouvelle copie en cours ... + +m.java_download_failed = Impossible de t\u00e9l\u00e9charger automatiquement la \ + version de Java n\u00e9cessaire.\n\n\ + Veuillez vous rendre sur www.java.com et t\u00e9l\u00e9charger et installer la version \ + la plus r\u00e9cente de Java, avant d'ex\u00e9cuter l'application \u00e0 nouveau. + +m.java_unpack_failed = Impossible de d\u00e9compresser la version de \ + Java n\u00e9cessaire. Veuillez v\u00e9rifier que vous avez au moins 100 MB d'espace libre \ + sur votre disque dur puis tenter d'ex\u00e9cuter l'application \u00e0 nouveau.\n\n\ + Si le probl\u00e8me persiste, rendez vous www.java.com et t\u00e9l\u00e9chargez et \ + installez la version plus r\u00e9cente de Java puis essayez de nouveau. + +m.unable_to_repair = Impossible de t\u00e9l\u00e9charger les fichiers n\u00e9cessaires apr\u00e8s \ + cinq tentatives. Vous pouvez tenter d'ex\u00e9cuter l'application \u00e0 nouveau, mais il est \ + possible qu'une d\u00e9sinstallation / r\u00e9installation soit n\u00e9cessaire. + +m.unknown_error = Une erreur inconnue a fait \u00e9chouer le lancement de l'application. \ + Veuillez visiter\n{0} pour plus d'informations. +m.init_error = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause de l'erreur \ + suivante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations. + +m.readonly_error = Le r\u00e9pertoire d'installation de cette application: \ + \n{0}\nest en lecture seule. Veuillez installer l'application dans un r\u00e9pertoire avec \ + un acc\u00e8s en \u00e9criture. + +m.missing_resource = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause d'une \ + ressource manquante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations. + +m.insufficient_permissions_error = Vous n'avez pas accepter la signature \ + num\u00e9rique de cette application. Si vous souhaitez ex\u00e9cuter cette application, vous \ + devez accepter sa signature num\u00e9rique.\n\nAfin de le faire, vous devez quitter votre \ + navigateur, le red\u00e9marrer, retourner \u00e0 cette page puis relancer l'application. \ + Une fois la bo\u00eete de dialogue de s\u00e9curit\u00e9 affich\u00e9e, cliquez sur le bouton \ + pour accepter la signature num\u00e9rique et accorder les permissions n\u00e9cessaires au bon \ + fonctionnement de l'application. + +m.corrupt_digest_signature_error = Nous ne pouvons pas v\u00e9rifier la signature num\u00e9rique \ + de l'application.\nVeuillez v\u00e9rifier que vous lancez l'application \ndepuis \ + la bonne adresse internet. + +m.default_install_error = la section de support du site + +m.another_getdown_running = Plusieurs instances d'installation de cette \ + application sont d\u00e9j\u00e0 en cours d'ex\u00e9cution. Cette instance va s'arr\u00eater \ + afin de permettre aux autres d'aboutir. + +m.applet_stopped = L'appelet Getdown a \u00e9t\u00e9 stopp\u00e9e. + +# application/digest errors +m.missing_appbase = Le fichier de configuration ne contient pas 'appbase'. +m.invalid_version = Le fichier de configuration sp\u00e9cifie une version invalide. +m.invalid_appbase = Le fichier de configuration sp\u00e9cifie un 'appbase' invalide. +m.missing_class = Le fichier de configuration ne contient pas la classe de l'application. +m.missing_code = Le fichier de configuration ne sp\u00e9cifie aucune ressource de code. +m.invalid_digest_file = Le fichier digest est invalide. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_it.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_it.properties new file mode 100644 index 0000000..33b3260 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_it.properties @@ -0,0 +1,114 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Annullare l'installazione? +m.abort_confirm = Sei sicuro di voler annullare l'installazione? \ + Potrai riprenderla in seguito, riavviando nuovamente l'applicazione. +m.abort_ok = Chiudi +m.abort_cancel = Continua l'installazione + +m.detecting_proxy = Provo a recuperare le configurazioni del proxy + +m.configure_proxy = Impossibile collegarsi al server per \ + recuperare i dati. \ +

  • Se il Firewall di Windows o Norton Internet Security bloccano \ + javaw.exe non si possono scaricare i dati. Devi \ + permettere a javaw.exe di accedere a internet. Puoi provare \ + di nuovo, ma dovresti abilitare javaw.exe nella tua configurazione \ + del firewall ( Start -> Pannello di Controllo -> Windows Firewall ).
\ +

Il tuo computer potrebbe accedere a internet attraverso un proxy e \ + questo potrebbe non essere stato riconosciuto automaticamente. Se conosci le \ + tue impostazioni del proxy, puoi inserirle di seguito. + +m.proxy_extra = Se sei sicuro di non usare proxy \ + potrebbe essere un problema di internet o di collegamento con il server. \ + In questo caso puoi annullare e ripetere l'installazione più tardi. + +m.proxy_host = IP Proxy +m.proxy_port = Porta Proxy +m.proxy_username = Nome utente +m.proxy_password = Parola d'ordine +m.proxy_auth_required = Autenticazione richiesta +m.proxy_ok = OK +m.proxy_cancel = Annulla + +m.downloading_java = Scaricando la Java Virtual Machine +m.unpacking_java = Scompattando la Java Virtual Machine + +m.resolving = Recuperando i file da scaricare +m.downloading = Download dei dati +m.failure = Download fallito: {0} + +m.checking = Sto controllando gli aggiornamenti +m.validating = Validazione +m.patching = Applico le patch +m.launching = Avvio + +m.patch_notes = Note delle Patch +m.play_again = Avvia Nuovamente + +m.complete = {0}% completato +m.remain = {0} rimasto + +m.updating_metadata = Scarico i file di controllo + +m.init_failed = La configurazione è corrotta o mancante. Provo a \ + scaricarne una nuova copia... + +m.java_download_failed = Impossibile scaricare la versione corretta \ + di Java per il tuo computer.\n\n\ + Visita www.java.com e scarica l'ultima versione di \ + Java, poi lancia di nuovo l'applicazione. + +m.java_unpack_failed = Impossibile scompattare l'aggiornamento di \ + Java. Verifica di avere almeno 100 MB di spazio libero nel tuo \ + hard disk e prova a rilanciare l'applicazione.\n\n\ + Se l'errore persiste, vistia www.java.com, scarica e \ + installa l'ultima versione di Java e riprova. + +m.unable_to_repair = Impossibile scaricare i file necessari dopo 5 \ + tentativi. Puoi provare a rilanciare l'applicazione, ma se fallisce \ + di nuovo potresti dover reinstallarla. + +m.unknown_error = L'applicazione non è stata avviata a causa di uno strano \ + errore che non conosco. Visita\n{0} per avere informazioni \ + in merito. +m.init_error = L'applicazione non è stata avviata a causa del seguente \ + errore:\n{0}\n\nVistita\n{1} per avere \ + informazioni su come risolvere il problema. + +m.readonly_error = La directory dove l'applicazione è installata: \ + \n{0}\nè in sola lettura. Installa l'applicazione dove hai i diritti \ + di scrittura. + +m.missing_resource = L'applicazione non è stata avviata a causa di mancanza \ + di risorse:\n{0}\n\nVisita\n{1} per avere informazioni su come risolvere \ + questi problemi. + +m.insufficient_permissions_error = Non hai accettato la \ + firma digitale. Se vuoi eseguire l'applicazione devi accettare la \ + firma digitale.\n\nPer farlo, riavvia il tuo browser \ + e ritorna in questa pagina per rilanciare l'applicazione. Quando l'avviso \ + di sicurezza viene mostrato, clicca per accettare la firma digitale \ + ed eseguire l'applicazione con i privilegi necessari. + +m.corrupt_digest_signature_error = Impossibile verificare la firma digitale dell'applicazione \ + .\nControlla di aver lanciato l'applicazione dal\n\ + sito web corretto. + +m.default_install_error = la sezione di supporto del sito + +m.another_getdown_running = E' già in esecuzione un'istanza del programma. \ + Questa verrà chiusa. + +m.applet_stopped = L'applet di Getdown è stata interrotta. + +# application/digest errors +m.missing_appbase = Il tag "appbase" è mancante. +m.invalid_version = Il file di configurazione non contiene una versione valida (tag "version"). +m.invalid_appbase = Il tag "appbase" non è valido. +m.missing_class = Il file di configurazione non contiene la classe da eseguire (tag "class"). +m.missing_code = Il file di configurazione non contiene alcuna risorsa (tag "code"). +m.invalid_digest_file = Il file di digest non è valido. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ja.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ja.properties new file mode 100644 index 0000000..c344c16 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ja.properties @@ -0,0 +1,107 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f +m.abort_confirm = \u672c\u5f53\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f \ + \u5f8c\u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u305f\u969b\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u518d\u958b\u3067\u304d\u307e\u3059\u3002 +m.abort_ok = \u4e2d\u6b62 +m.abort_cancel = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u306e\u7d9a\u884c + +m.detecting_proxy = \u81ea\u52d5\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u5b9f\u884c\u4e2d + +m.configure_proxy = \u30b5\u30fc\u30d0\u306b\u63a5\u7d9a\u3067\u304d\u306a\u3044\u305f\u3081\u3001\u30b2\u30fc\u30e0\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b \ + \u5931\u6557\u3057\u307e\u3057\u305f\u3002 \ +

  • \u30a6\u30a3\u30f3\u30c9\u30a6\u30ba\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u307e\u305f\u306f\u30ce\u30fc\u30c8\u30f3\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u304c \ + javaw.exe\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b\u3088\u3046\u8a2d\u5b9a\u3057\u3066\u3042\u308b\u5834\u5408\u306f\u3001\u30b2\u30fc\u30e0\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3002 \u8a2d\u5b9a\u3092 \ + javaw.exe\u7d4c\u7531\u3067\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u3088\u3046\u306b\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \u30b2\u30fc\u30e0\u3092\u518d\u8d77\u52d5 \ + \u3057\u305f\u5f8c\u3001\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u306e\u8a2d\u5b9a\u304b\u3089javaw.exe \u3092\u524a\u9664 \ + \u3057\u3066\u304f\u3060\u3055\u3044\uff08\u30b9\u30bf\u30fc\u30c8\u2192\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d1\u30cd\u30eb\u2192\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\uff09\u3002
\ +

\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u81ea\u52d5\u691c\u51fa\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306f \ + \u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u3078\u30a2\u30af\u30bb\u30b9\u3057\u3066\u3044\u307e\u3059\u3002 \u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u8a73\u7d30\u304c \ + \u308f\u304b\u3063\u3066\u3044\u308b\u5834\u5408\u306f\u3001\u4e0b\u306b\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.proxy_extra = \u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001\u4e00\u6642\u7684\u306a \ + \u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306e\u4e0d\u5177\u5408\u306b\u3088\u308a\u3001\u30b5\u30fc\u30d0\u3068\u4ea4\u4fe1\u3067\u304d\u306a\u3044\u72b6\u614b\u306b\u3042\u308b \ + \u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002 \u305d\u306e\u5834\u5408\u306f\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u3066\u3001 \ + \u5f8c\u307b\u3069\u6539\u3081\u3066\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.proxy_host = \u30d7\u30ed\u30ad\u30b7IP +m.proxy_port = \u30d7\u30ed\u30ad\u30b7\u30dd\u30fc\u30c8 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = \u30ad\u30e3\u30f3\u30bb\u30eb + +m.downloading_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d +m.unpacking_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u89e3\u51cd\u4e2d + +m.resolving = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306e\u8a2d\u5b9a\u4e2d +m.downloading = \u30c7\u30fc\u30bf\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d +m.failure = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u5931\u6557\uff1a {0} + +m.checking = \u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u306e\u78ba\u8a8d\u4e2d +m.validating = \u8a8d\u8a3c\u4e2d +m.patching = \u4fee\u6b63\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u5b9f\u884c\u4e2d +m.launching = \u5b9f\u884c\u4e2d + +m.complete = {0}\uff05\u5b8c\u4e86 +m.remain = \u3000\u6b8b\u308a{0} + +m.updating_metadata = \u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d + +m.init_failed = \u74b0\u5883\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3001\u307e\u305f\u306f\u58ca\u308c\u3066\u3044\u307e\u3059\u3002 \u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \ + \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d\u2026 + +m.java_download_failed = \u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306b\u3001Java\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u6700\u65b0 \ + \u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u81ea\u52d5\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\n \ + www.java.com \u304b\u3089\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u624b\u52d5\u3067\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u3001 \ + \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.java_unpack_failed = Java\u306e\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u89e3\u51cd \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u30cf\u30fc\u30c9\u30c9\u30e9\u30a4\u30d6\u306e\u30e1\u30e2\u30ea\u304c100MB\u4ee5\u4e0a\u3042\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304b\u3089 \ + \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n \ + \u554f\u984c\u304c\u89e3\u6c7a\u3057\u306a\u3044\u5834\u5408\u306f\u3001www.java.com \u304b\u3089Java\u306e\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \ + \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u304b\u3089\u3001\u518d\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002 + +m.unable_to_repair = 5\u56de\u8a66\u884c\u3057\u307e\u3057\u305f\u304c\u3001\u5fc5\u8981\u306a\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9 \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u5f8c\u307b\u3069\u6539\u3081\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \ + \u518d\u5ea6\u5931\u6557\u3057\u305f\u5834\u5408\u306f\u3001\u30a2\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u5f8c\u306b\u518d\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.unknown_error = \u539f\u56e0\u4e0d\u660e\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c \ + \u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u89e3\u6c7a\u65b9\u6cd5\u3092\n{0}\u3067 \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +m.init_error = \u6b21\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \ + \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067 \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.readonly_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u305f\u30d5\u30a9\u30eb\u30c0\u306f \ + \n{0}\n\u8aad\u307f\u53d6\u308a\u5c02\u7528\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u66f8\u304d\u8fbc\u307f\u304c\u3067\u304d\u308b\u30d5\u30a9\u30eb\u30c0\u306b \ + \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.missing_resource = \u30ea\u30bd\u30fc\u30b9\u4e0d\u660e\u306e\u305f\u3081\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \ + \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067\u78ba\u8a8d \ + \u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.insufficient_permissions_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u62d2\u5426 \ + \u3055\u308c\u307e\u3057\u305f\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u5834\u5408\u306f\u3001\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u306e\u627f\u8a8d\u304c \ + \u5fc5\u8981\u3067\u3059\u3002\n\n\u627f\u8a8d\u306b\u306f\u3001\u30d6\u30e9\u30a6\u30b6\u3092\u9589\u3058\u3066\u304b\u3089\u518d\u5ea6\u958b\u304d\u3001 \ + \u672c\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3092\u518d\u8868\u793a\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u5ea6\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044 \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e \ + \u8b66\u544a\u304c\u8868\u793a\u3055\u308c\u305f\u6642\u306f\u3001\u5b9f\u884c\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u3092\u627f\u8a8d\u3057\u3001 \ + \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.corrupt_digest_signature_error = \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u8a8d\u8a3c \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\u6307\u5b9a\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u3044\u308b\u304b\n \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.default_install_error = \u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3067\u306e\u30b5\u30dd\u30fc\u30c8\u8868\u793a + +# application/digest errors +m.missing_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eappbase\u304c\u4e0d\u660e\u3067\u3059\u3002 +m.invalid_version = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306f\u7121\u52b9\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 +m.invalid_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u306aappbase\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 +m.missing_class = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30af\u30e9\u30b9\u304c\u4e0d\u660e\u3067\u3059\u3002 +m.missing_code = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u3067\u30b3\u30fc\u30c9\u30ea\u30bd\u30fc\u30b9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +m.invalid_digest_file = \u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u3067\u3059\u3002 diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ko.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ko.properties new file mode 100644 index 0000000..3f8a47f --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ko.properties @@ -0,0 +1,102 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? +m.abort_confirm = \uC815\uB9D0\uB85C \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \ + \uB098\uC911\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uC5EC \uC124\uCE58\uB97C \uC7AC\uAC1C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. +m.abort_ok = \uC911\uC9C0 +m.abort_cancel = \uACC4\uC18D\uD558\uC5EC \uC124\uCE58 + +m.detecting_proxy = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4 + +m.configure_proxy = \uAC8C\uC784 \uB370\uC774\uD130\uB97C \uBC1B\uAE30 \uC704\uD55C \uC11C\uBC84 \uC811\uC18D\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4.\ +

  • \uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD \uB610\uB294 \uB178\uD134 \uC778\uD130\uB137 \uC2DC\uD050\uB9AC\uD2F0\uAC00 javaw.exe\uC774 \uC124\uC815\uC5D0\uC11C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC744 \uACBD\uC6B0, \ + \uAC8C\uC784 \uB370\uC774\uD130\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \ + javaw.exe\uAC00 \uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD560 \uC218 \uC788\uB3C4\uB85D \uC124\uC815\uC744 \uBCC0\uACBD\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + \uAC8C\uC784\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD55C \uD6C4, \uBC29\uD654\uBCBD \uC124\uC815\uC5D0\uC11C javaw.exe\uB97C \uC0AD\uC81C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + ( \uC2DC\uC791 -> \uC81C\uC5B4\uD310 -> \uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD )
\ +

\uCEF4\uD4E8\uD130\uAC00 \uD504\uB85D\uC2DC \uC11C\uBC84\uB97C \uD1B5\uD574 \uC778\uD130\uB137\uC5D0 \uC5F0\uACB0\uB418\uC5B4 \uC788\uB2E4\uBA74, \uD504\uB85D\uC2DC \uC124\uC815\uC758 \uC790\uB3D9 \uAD6C\uC131\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC73C\uBBC0\uB85C, \ + \uC0AC\uC6A9\uD558\uB294 \uD504\uB85D\uC2DC \uC124\uC815\uC744 \uC54C\uACE0 \uC788\uC744 \uACBD\uC6B0 \uC544\uB798\uC5D0 \uC785\uB825\uD558\uC5EC \uC8FC\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.proxy_extra = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4 + +m.proxy_host = \uD504\uB85D\uC2DC IP +m.proxy_port = \uD504\uB85D\uC2DC \uD3EC\uD2B8 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = \uCDE8\uC18C + +m.downloading_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uB2E4\uC6B4\uB85C\uB4DC \uC911 +m.unpacking_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uC555\uCD95\uC744 \uD574\uC81C\uD558\uB294 \uC911 + +m.resolving = \uB2E4\uC6B4\uB85C\uB4DC \uBD84\uC11D \uC911 +m.downloading = \uB370\uC774\uD130 \uB2E4\uC6B4\uB85C\uB4DC \uC911 +m.failure = \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328: {0} + +m.checking = \uC5C5\uB370\uC774\uD2B8 \uCCB4\uD06C +m.validating = \uC720\uD6A8\uC131 \uAC80\uC0AC \uC911 +m.patching = \uD328\uCE58 \uC911 +m.launching = \uC2E4\uD589 \uC911 + +m.patch_notes = \uD328\uCE58 \uB178\uD2B8 +m.play_again = \uB2E4\uC2DC \uC2E4\uD589 + +m.complete = {0}% \uC644\uB8CC +m.remain = {0} \uB0A8\uC74C + +m.updating_metadata = \uCEE8\uD2B8\uB864 \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911 + +m.init_failed = \uC124\uC815 \uD30C\uC77C\uC774 \uB204\uB77D\uB418\uC5C8\uAC70\uB098 \uBCC0\uD615\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \ + \uC0C8\uB85C\uC6B4 \uBCF5\uC0AC\uBCF8\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911\uC785\uB2C8\uB2E4... + +m.java_download_failed = \uC774 \uCEF4\uD4E8\uD130\uC5D0 \uD544\uC694\uD55C \uC0C8\uB85C\uC6B4 \uBC84\uC804\uC758 \uC790\uBC14\uB97C \uC790\uB3D9\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n\n\ + \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624. + +m.java_unpack_failed = \uC5C5\uB370\uC774\uD2B8\uB41C \uBC84\uC804\uC758 \uC790\uBC14\uC758 \uC555\uCD95\uC744 \uD480 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \ + \uD558\uB4DC\uB4DC\uB77C\uC774\uBE0C\uC5D0 \uCD5C\uC18C\uD55C 100MB\uC758 \uC6A9\uB7C9\uC744 \uD655\uBCF4\uD55C \uC774\uD6C4, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624.\n\n\ + \uB9CC\uC57D \uBB38\uC81C\uAC00 \uD574\uACB0\uB418\uC9C0 \uC54A\uB294\uB2E4\uBA74, \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624. + +m.unable_to_repair = \uB2E4\uC12F\uBC88\uC758 \uC2DC\uB3C4\uC5D0\uB3C4 \uD544\uC694\uD55C \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD574\uBCF4\uC2DC\uACE0, \uADF8\uB798\uB3C4 \uB2E4\uC6B4\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD55C\uB2E4\uBA74, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC81C\uAC70\uD55C \uD6C4, \uB2E4\uC2DC \uC2E4\uD589\uD574\uBCF4\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.unknown_error = \uBCF5\uAD6C\uB420 \uC218 \uC5C6\uB294 \uC624\uB958\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \ + \n{0}\uC5D0 \uB300\uD55C \uBCF5\uAD6C \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.init_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC544\uB798\uC640 \uAC19\uC740 \uC5D0\uB7EC\uB85C \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC5D0\uB7EC:\ + \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.readonly_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC124\uCE58\uB41C \uB514\uB809\uD1A0\uB9AC: \ + \n{0}\n\uAC00 \uC77D\uAE30 \uC804\uC6A9\uC785\uB2C8\uB2E4. \uC77D\uAE30 \uAD8C\uD55C\uC774 \uC2B9\uC778\uB41C \uB809\uD1A0\uB9AC\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC124\uCE58\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.missing_resource = \uB9AC\uC18C\uC2A4\uC758 \uC190\uC2E4\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. : \ + \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.insufficient_permissions_error = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uAE30 \uC704\uD574\uC11C \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + \n\n\uADF8\uB9AC\uACE0 \uB098\uC11C \uC6F9 \uBE0C\uB77C\uC6B0\uC800\uB97C \uB2EB\uACE0 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC5EC \uC6F9\uD398\uC774\uC9C0\uB85C \uB3CC\uC544\uC640 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC7AC\uC2DC\uC791\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. \ + \uBCF4\uC548\uC5D0 \uB300\uD55C \uB300\uD654\uC0C1\uC790\uAC00 \uBCF4\uC774\uBA74, \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC5D0 \uB300\uD55C \uD655\uC778 \uBC84\uD2BC\uC744 \uD074\uB9AD\uD558\uACE0, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uAE30 \uC704\uD55C \ + \uAD8C\uD55C\uC744 \uBD80\uC5EC\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.corrupt_digest_signature_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n \ + \uC62C\uBC14\uB978 \uC6F9\uC0AC\uC774\uD2B8\uC5D0\uC11C \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uACE0 \uC788\uB294 \uC9C0 \uD655\uC778\uBC14\uB78D\uB2C8\uB2E4. + +m.default_install_error = \uC6F9\uC0AC\uC774\uD2B8\uC758 \uC9C0\uC6D0 \uBA54\uB274(support section)\uB97C \uD655\uC778\uD558\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.another_getdown_running = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uC778\uC2A4\uD1A8\uB7EC\uC758 \uB2E4\uC911 \uC778\uC2A4\uD134\uC2A4\uAC00 \uC2E4\uD589\uC911\uC785\uB2C8\uB2E4. \ + \uD558\uB098\uAC00 \uC644\uB8CC\uB420 \uB54C\uAE4C\uC9C0 \uC911\uB2E8\uB429\uB2C8\uB2E4. + +m.applet_stopped = Getdown \uC560\uD50C\uB9BF \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. + +# application/digest errors +m.missing_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 'appbase' \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. +m.invalid_version = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C \uBC84\uC804\uC774 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. +m.invalid_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C 'appbase'\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. +m.missing_class = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uD074\uB798\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. +m.missing_code = \uC124\uC815 \uD30C\uC77C\uC5D0 \uB9AC\uC18C\uC2A4\uC5D0 \uB300\uD55C \uCF54\uB4DC\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +m.invalid_digest_file = \uB2E4\uC774\uC81C\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_pt.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_pt.properties new file mode 100644 index 0000000..47db91c --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_pt.properties @@ -0,0 +1,118 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Cancelar a instala\u00E7\u00E3o? +m.abort_confirm = Tem certeza que deseja cancelar a instala\u00E7\u00E3o? \ + Voc\u00EA pode continuar a instala\u00E7\u00E3o mais tarde, \ + basta executar a aplica\u00E7\u00E3o novamente. +m.abort_ok = Sair +m.abort_cancel = Continuar a instala\u00E7\u00E3o + +m.detecting_proxy = Tentando detectar automaticamente as configura\u00E7\u00F5es de proxy + +m.configure_proxy = N\u00E3o foi poss\u00EDvel conectar aos nossos servidores para \ + fazer o download dos dados. \ +

  • Se o Firewall do Windows ou o Norton Internet Security est\u00E1 configurado \ + para bloquear o programa javaw.exe n\u00E3o ser\u00E1 poss\u00EDvel realizar \ + o download. Voc\u00EA ter\u00E1 que permitir que o programa javaw.exe acesse \ + a internet. Voc\u00EA pode tentar executar o programa novamente, mas voc\u00EA precisa \ + remover o programa javaw.exe das configura\u00E7\u00F5es do firewall (Iniciar -> Painel \ + de controle -> Firewall do Windows).
\ +

Seu computador pode estar acessando a internet atrav\u00E9s de um proxy e n\u00E3o foi \ + capaz de detectar automaticamente as configura\u00E7\u00F5es de proxy. \ + Voc\u00EA pode informar esses dados abaixo. + +m.proxy_extra = Se voc\u00EA tem certeza que n\u00E3o usa um proxy, ent\u00E3o pode ser \ + que exista um problema tempor\u00E1rio que est\u00E1 impedindo a comunica\u00E7\u00E3o \ + com os nossos servidores. Neste caso voc\u00EA pode cancelar e tentar instalar novamente \ + mais tarde. + +m.proxy_host = IP do Proxy +m.proxy_port = Porta do Proxy +m.proxy_username = Nome de usu\u00e1rio +m.proxy_password = Senha +m.proxy_auth_required = Autentifica\u00e7\u00e3o requerida +m.proxy_ok = OK +m.proxy_cancel = Cancelar + +m.downloading_java = Fazendo o download da m\u00E1quina virtual Java +m.unpacking_java = Descompactando a m\u00E1quina virtual Java + +m.resolving = Resolvendo downloads +m.downloading = Transferindo dados +m.failure = Download falhou: {0} + +m.checking = Verificando atualiza\u00E7\u00F5es +m.validating = Validando +m.patching = Atualizando +m.launching = Executando + +m.patch_notes = Corrigir notas +m.play_again = Jogar de novo + +m.complete = {0}% completo +m.remain = {0} Permanecer + +m.updating_metadata = Transferindo arquivos de controle + +m.init_failed = Nosso arquivo de configura\u00E7\u00E3o est\u00E1 ausente ou corrompido. Tente \ + baixar uma nova c\u00F3pia... + +m.java_download_failed = N\u00E3o conseguimos baixar automaticamente a\ + vers\u00E3o necess\u00E1ria do Java para o seu computador.\n\n\ + Por favor, acesse www.java.com, baixe e instale a \u00FAltima vers\u00E3o do \ + Java, em seguida, tente executar o aplicativo novamente. + +m.java_unpack_failed = N\u00E3o conseguimos descompactar uma vers\u00E3o atualizada do \ + Java. Por favor, certifique-se de ter pelo menos 100 MB de espa\u00E7o livre em seu \ + disco r\u00EDgido e tente executar o aplicativo novamente. \n\n\ + Se isso n\u00E3o resolver o problema, acesse www.java.com,baixe e \ + instale a \u00FAltima vers\u00E3o do Java e tente novamente. + +m.unable_to_repair = N\u00E3o conseguimos baixar os arquivos necess\u00E1rios depois de \ + cinco tentativas. Voc\u00EA pode tentar executar o aplicativo novamente, mas se ele \ + falhar pode ser necess\u00E1rio desinstalar e reinstalar. + +m.unknown_error = A aplica\u00E7\u00E3o falhou ao iniciar devido a algum erro estranho \ + do qual n\u00E3o conseguimos recuperar. Por favor, visite \n{0} para obter \ + informa\u00E7\u00F5es sobre como recuperar. +m.init_error = A aplica\u00E7\u00E3o falhou ao iniciar devido ao seguinte \ + erro:\n{0}\n\nPor favor visite \n{1} para \ + informa\u00E7\u00F5es sobre como lidar com esse problema. + +m.readonly_error =O diret\u00F3rio no qual este aplicativo est\u00E1 instalado: \ + \n{0}\n \u00E9 somente leitura. Por favor, instale o aplicativo em um diret\u00F3rio onde \ + voc\u00EA tem acesso de grava\u00E7\u00E3o. + +m.missing_resource = A aplica\u00E7\u00E3o falhou ao iniciar devido a uma falta \ + de recurso:\n{0}\n\n Por favor, visite\n{1} para obter informa\u00E7\u00F5es sobre \ + como lidar com tal problema. + +m.insufficient_permissions_error = Voc\u00EA n\u00E3o aceitou a assinatura digital \ + do aplicativo. Se voc\u00EA quiser executar o aplicativo, voc\u00EA ter\u00E1 que aceitar \ + a assinatura digital. \n\nPara fazer isso, voc\u00EA ter\u00E1 que sair do seu navegador, \ + reinici\u00E1-lo, e retornar a esta p\u00E1gina web para executar a aplica\u00E7\u00E3o. \ + Quando o di\u00E1logo de seguran\u00E7a aparecer, clique no bot\u00E3o para aceitar a \ + assinatura digital e conceder a este aplicativo os privil\u00E9gios necess\u00E1rios \ + para executar. + +m.corrupt_digest_signature_error = N\u00E3o conseguimos verificar a assinatura digital \ + do aplicativo.\nPor favor, verifique se voc\u00EA est\u00E1 utilizando o aplicativo \nde um \ + site correto. + +m.default_install_error = a se\u00E7\u00E3o de suporte do site + +m.another_getdown_running = V\u00E1rias inst\u00E2ncias desta aplica\u00E7\u00E3o \ + est\u00E3o em execu\u00E7\u00E3o. Esta ir\u00E1 parar e deixar outra completar suas atividades. + +m.applet_stopped = Foi solicitado ao miniaplicativo GetDow que parasse de trabalhar. + +# application/digest errors +m.missing_appbase = O arquivo de configura\u00E7\u00E3o n\u00E3o possui o 'AppBase'. +m.invalid_version = O arquivo de configura\u00E7\u00E3o especifica uma vers\u00E3o inv\u00E1lida. +m.invalid_appbase = O arquivo de configura\u00E7\u00E3o especifica um 'AppBase' inv\u00E1lido. +m.missing_class = O arquivo de configura\u00E7\u00E3o n\u00E3o possui a classe de aplicativo. +m.missing_code = O arquivo de configura\u00E7\u00E3o n\u00E3o especifica um recurso de c\u00F3digo. +m.invalid_digest_file = O arquivo digest \u00E9 inv\u00E1lido. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_zh.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_zh.properties new file mode 100644 index 0000000..2c27543 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_zh.properties @@ -0,0 +1,61 @@ +# +# $Id$ +# +# Getdown translation messages + +m.detecting_proxy = \u641c\u5bfb\u4ee3\u7406\u670d\u52a1\u5668 + +m.configure_proxy = \u6211\u4eec\u65e0\u6cd5\u8fde\u63a5\u5230\u670d\u52a1\u5668\u4e0b\u8f7d\u6e38\u620f\u6570\u636e\u3002\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e \ + \u60a8\u7684\u8ba1\u7b97\u673a\u662f\u901a\u8fc7\u4ee3\u7406\u670d\u52a1\u5668\u8fde\u63a5\u4e92\u8054\u7f51\u7684\uff0c\u5e76\u4e14\u6211\u4eec\u65e0\u6cd5\u81ea\u52a8\u83b7\u5f97\u4ee3\u7406\u670d\u52a1\u5668\u7684 \ + \u8bbe\u7f6e\u3002\u5982\u679c\u60a8\u77e5\u9053\u60a8\u4ee3\u7406\u670d\u52a1\u5668\u7684\u8bbe\u7f6e\uff0c\u60a8\u53ef\u4ee5\u5728\u4e0b\u9762\u8f93\u5165\u3002 + +m.proxy_extra = \u5982\u679c\u60a8\u786e\u5b9a\u60a8\u6ca1\u6709\u4f7f\u7528\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e\u6682\u65f6\u65e0\u6cd5 \ + \u8fde\u63a5\u5230\u4e92\u8054\u7f51\uff0c\u5bfc\u81f4\u65e0\u6cd5\u548c\u670d\u52a1\u5668\u901a\u8baf\u3002\u8fd9\u79cd\u60c5\u51b5\uff0c\u60a8\u53ef\u4ee5\u53d6\u6d88\uff0c\u7a0d\u5019\u518d\u91cd\u65b0\u5b89\u88c5\u3002

\ + \u5982\u679c\u60a8\u65e0\u6cd5\u786e\u5b9a\u60a8\u662f\u5426\u4f7f\u7528\u4e86\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8bf7\u8bbf\u95ee\u6211\u4eec\u7f51\u7ad9\u4e2d\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c \ + \u4e86\u89e3\u5982\u4f55\u68c0\u6d4b\u60a8\u7684\u4ee3\u7406\u670d\u52a1\u5668\u8bbe\u7f6e\u3002 + +m.proxy_host = \u4ee3\u7406\u670d\u52a1\u5668\u7684IP\u5730\u5740 +m.proxy_port = \u4ee3\u7406\u670d\u52a1\u5668\u7684\u7aef\u53e3\u53f7 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = \u786e\u5b9a +m.proxy_cancel = \u53d6\u6d88 + +m.resolving = \u5206\u6790\u9700\u4e0b\u8f7d\u5185\u5bb9 +m.downloading = \u4e0b\u8f7d\u6570\u636e +m.failure = \u4e0b\u8f7d\u5931\u8d25: {0} + +m.checking = \u68c0\u67e5\u66f4\u65b0\u5185\u5bb9 +m.validating = \u786e\u8ba4 +m.patching = \u5347\u7ea7 +m.launching = \u542f\u52a8 + +m.complete = {0}% \u5b8c\u6210 +m.remain = {0} \u5269\u4f59\u65f6\u95f4 + +m.updating_metadata = \u4e0b\u8f7d\u63a7\u5236\u6587\u4ef6 + +m.init_failed = \u65e0\u6cd5\u627e\u5230\u914d\u7f6e\u6587\u4ef6\u6216\u5df2\u635f\u574f\u3002\u5c1d\u8bd5\u91cd\u65b0\u4e0b\u8f7d... + +m.unable_to_repair = \u7ecf\u8fc75\u6b21\u5c1d\u8bd5\uff0c\u4f9d\u7136\u65e0\u6cd5\u4e0b\u8f7d\u6240\u9700\u7684\u6587\u4ef6\u3002\ +\u60a8\u53ef\u4ee5\u91cd\u65b0\u8fd0\u884c\u7a0b\u5e8f\uff0c\u4f46\u662f\u5982\u679c\u4f9d\u7136\u5931\u8d25\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u53cd\u5b89\u88c5\u5e76\u91cd\u65b0\u5b89\u88c5\u3002 + + +m.unknown_error = \u7531\u4e8e\u4e00\u4e9b\u65e0\u6cd5\u56de\u590d\u7684\u4e25\u91cd\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\u3002\ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u89e3\u51b3\u95ee\u9898\u3002 + +m.init_error = \u7531\u4e8e\u4e0b\u5217\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u9519\u8bef\u3002 + + +m.missing_resource = \u7531\u4e8e\u65e0\u6cd5\u627e\u5230\u4e0b\u5217\u8d44\u6e90\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u95ee\u9898\u3002 + +# application/digest errors +m.missing_appbase = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230 'appbase'\u3002 +m.invalid_version = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684\u7248\u672c\u3002 +m.invalid_appbase = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684 'appbase'\u3002 +m.missing_class = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u7a0b\u5e8f\u6587\u4ef6\u3002 +m.missing_code = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u6307\u5b9a\u7684\u8d44\u6e90\u3002 +m.invalid_digest_file = \u65e0\u6548\u7684\u914d\u7f6e\u6587\u4ef6\u3002 diff --git a/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT.jar b/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..f71b6ee44704b4963085300165a849c456b31190 GIT binary patch literal 195005 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[O5?6>0`+zj1mJVrRG!$T>oXiQ1E;haX(k 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
)3 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 Zq)$->5y&V4K`ij66^QC*&c1)c{2xiT0BryO literal 0 HcmV?d00001 diff --git a/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT_proguard_base.jar b/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT_proguard_base.jar new file mode 100644 index 0000000000000000000000000000000000000000..88428a457050f24c98895c62bea04cb4167b9a43 GIT binary patch literal 73485 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 literal 0 HcmV?d00001 diff --git a/getdown/src/getdown/launcher/target/maven-archiver/pom.properties b/getdown/src/getdown/launcher/target/maven-archiver/pom.properties new file mode 100644 index 0000000..ab0c975 --- /dev/null +++ b/getdown/src/getdown/launcher/target/maven-archiver/pom.properties @@ -0,0 +1,4 @@ +#Created by Apache Maven 3.5.2 +version=1.8.3-SNAPSHOT +groupId=com.threerings.getdown +artifactId=getdown-launcher diff --git a/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..ecc6201 --- /dev/null +++ b/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,28 @@ +com/threerings/getdown/launcher/GetdownApp$1$1.class +com/threerings/getdown/launcher/Getdown$4.class +com/threerings/getdown/launcher/RotatingBackgrounds$ImageLoader.class +com/threerings/getdown/launcher/ProxyPanel$SaneTextField.class +com/threerings/getdown/launcher/GetdownApp$2.class +com/threerings/getdown/launcher/Getdown$2.class +com/threerings/getdown/launcher/ProxyPanel.class +com/threerings/getdown/launcher/GetdownApp.class +com/threerings/getdown/launcher/Getdown$ProgressReporter.class +com/threerings/getdown/launcher/ProxyPanel$SaneLabelField.class +com/threerings/getdown/launcher/ProxyUtil$1.class +com/threerings/getdown/launcher/ProxyPanel$SanePasswordField.class +com/threerings/getdown/launcher/StatusPanel$1.class +com/threerings/getdown/launcher/GetdownApp$1$3.class +com/threerings/getdown/launcher/AbortPanel.class +com/threerings/getdown/launcher/GetdownApp$1$2.class +com/threerings/getdown/launcher/ProxyUtil.class +com/threerings/getdown/launcher/ProxyPanel$1.class +com/threerings/getdown/launcher/Getdown$5.class +com/threerings/getdown/launcher/StatusPanel.class +com/threerings/getdown/launcher/GetdownApp$1.class +com/threerings/getdown/launcher/RotatingBackgrounds.class +com/threerings/getdown/launcher/MultipleGetdownRunning.class +com/threerings/getdown/launcher/Getdown$1.class +com/threerings/getdown/launcher/Getdown.class +com/threerings/getdown/launcher/Getdown$3.class +com/threerings/getdown/launcher/Getdown$6.class +com/threerings/getdown/launcher/Getdown$4$1.class diff --git a/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..5ad07dd --- /dev/null +++ b/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,8 @@ +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java diff --git a/getdown/src/getdown/launcher/target/proguard_map.txt b/getdown/src/getdown/launcher/target/proguard_map.txt new file mode 100644 index 0000000..4ecff17 --- /dev/null +++ b/getdown/src/getdown/launcher/target/proguard_map.txt @@ -0,0 +1,1349 @@ +ca.beq.util.win32.registry.KeyIterator -> ca.beq.util.win32.registry.KeyIterator: + ca.beq.util.win32.registry.RegistryKey m_key -> m_key + int m_index -> m_index + int m_hkey -> m_hkey + int m_maxsize -> m_maxsize + int m_count -> m_count + void (ca.beq.util.win32.registry.RegistryKey) -> + void initializeFields() -> initializeFields + boolean hasNext() -> hasNext + java.lang.Object next() -> next + java.lang.String getNext() -> getNext + void remove() -> remove +ca.beq.util.win32.registry.RegistryException -> ca.beq.util.win32.registry.RegistryException: + void () -> + void (java.lang.String) -> +ca.beq.util.win32.registry.RegistryKey -> ca.beq.util.win32.registry.RegistryKey: + boolean c_initSucceeded -> c_initSucceeded + ca.beq.util.win32.registry.RootKey m_root -> m_root + java.lang.String m_path -> m_path + void testInitialized() -> testInitialized + void initialize() -> initialize + void initialize(java.lang.String) -> initialize + boolean isInitialized() -> isInitialized + void checkInitialized() -> checkInitialized + void () -> + void (ca.beq.util.win32.registry.RootKey) -> + void (java.lang.String) -> + void (ca.beq.util.win32.registry.RootKey,java.lang.String) -> + ca.beq.util.win32.registry.RootKey getRootKey() -> getRootKey + java.lang.String getPath() -> getPath + java.lang.String makePath(java.lang.String) -> makePath + java.lang.String getName() -> getName + boolean exists() -> exists + void create() -> create + ca.beq.util.win32.registry.RegistryKey createSubkey(java.lang.String) -> createSubkey + void delete() -> delete + boolean hasSubkeys() -> hasSubkeys + boolean hasSubkey(java.lang.String) -> hasSubkey + java.util.Iterator subkeys() -> subkeys + java.util.Iterator values() -> values + boolean hasValue(java.lang.String) -> hasValue + boolean hasValues() -> hasValues + ca.beq.util.win32.registry.RegistryValue getValue(java.lang.String) -> getValue + void setValue(ca.beq.util.win32.registry.RegistryValue) -> setValue + void deleteValue(java.lang.String) -> deleteValue + java.lang.String toString() -> toString + void () -> +ca.beq.util.win32.registry.RegistryValue -> ca.beq.util.win32.registry.RegistryValue: + java.lang.String m_name -> m_name + ca.beq.util.win32.registry.ValueType m_type -> m_type + java.lang.Object m_data -> m_data + void () -> + void (java.lang.Object) -> + void (java.lang.String,java.lang.Object) -> + void (java.lang.String,ca.beq.util.win32.registry.ValueType,java.lang.Object) -> + void (java.lang.String,boolean) -> + void (java.lang.String,byte) -> + void (java.lang.String,int) -> + void (java.lang.String,long) -> + void (java.lang.String,float) -> + void (java.lang.String,double) -> + java.lang.String getName() -> getName + void setName(java.lang.String) -> setName + ca.beq.util.win32.registry.ValueType getType() -> getType + void setType(ca.beq.util.win32.registry.ValueType) -> setType + java.lang.Object getData() -> getData + void setData(java.lang.Object) -> setData + void setData(byte) -> setData + void setData(boolean) -> setData + void setData(int) -> setData + void setData(long) -> setData + void setData(float) -> setData + void setData(double) -> setData + java.lang.String getStringValue() -> getStringValue + java.lang.String toString() -> toString +ca.beq.util.win32.registry.RootKey -> ca.beq.util.win32.registry.RootKey: + java.lang.String m_name -> m_name + int m_value -> m_value + ca.beq.util.win32.registry.RootKey HKEY_CLASSES_ROOT -> HKEY_CLASSES_ROOT + ca.beq.util.win32.registry.RootKey HKEY_CURRENT_USER -> HKEY_CURRENT_USER + ca.beq.util.win32.registry.RootKey HKEY_LOCAL_MACHINE -> HKEY_LOCAL_MACHINE + ca.beq.util.win32.registry.RootKey HKEY_USERS -> HKEY_USERS + ca.beq.util.win32.registry.RootKey HKEY_CURRENT_CONFIG -> HKEY_CURRENT_CONFIG + ca.beq.util.win32.registry.RootKey HKEY_PERFORMANCE_DATA -> HKEY_PERFORMANCE_DATA + ca.beq.util.win32.registry.RootKey HKEY_DYN_DATA -> HKEY_DYN_DATA + void (java.lang.String,int) -> + int getValue() -> getValue + java.lang.String toString() -> toString + void () -> +ca.beq.util.win32.registry.ValueIterator -> ca.beq.util.win32.registry.ValueIterator: + ca.beq.util.win32.registry.RegistryKey m_key -> m_key + int m_index -> m_index + int m_hkey -> m_hkey + int m_maxsize -> m_maxsize + int m_count -> m_count + void (ca.beq.util.win32.registry.RegistryKey) -> + void initializeFields() -> initializeFields + boolean hasNext() -> hasNext + java.lang.Object next() -> next + java.lang.String getNext() -> getNext + void remove() -> remove +ca.beq.util.win32.registry.ValueType -> ca.beq.util.win32.registry.ValueType: + java.lang.String m_name -> m_name + int m_value -> m_value + ca.beq.util.win32.registry.ValueType REG_NONE -> REG_NONE + ca.beq.util.win32.registry.ValueType REG_SZ -> REG_SZ + ca.beq.util.win32.registry.ValueType REG_EXPAND_SZ -> REG_EXPAND_SZ + ca.beq.util.win32.registry.ValueType REG_BINARY -> REG_BINARY + ca.beq.util.win32.registry.ValueType REG_DWORD -> REG_DWORD + ca.beq.util.win32.registry.ValueType REG_DWORD_LITTLE_ENDIAN -> REG_DWORD_LITTLE_ENDIAN + ca.beq.util.win32.registry.ValueType REG_DWORD_BIG_ENDIAN -> REG_DWORD_BIG_ENDIAN + ca.beq.util.win32.registry.ValueType REG_MULTI_SZ -> REG_MULTI_SZ + ca.beq.util.win32.registry.ValueType REG_RESOURCE_LIST -> REG_RESOURCE_LIST + ca.beq.util.win32.registry.ValueType REG_LINK -> REG_LINK + ca.beq.util.win32.registry.ValueType REG_FULL_RESOURCE_DESCRIPTOR -> REG_FULL_RESOURCE_DESCRIPTOR + ca.beq.util.win32.registry.ValueType REG_RESOURCE_REQUIREMENTS_LIST -> REG_RESOURCE_REQUIREMENTS_LIST + void (java.lang.String,int) -> + int getValue() -> getValue + java.lang.String toString() -> toString + void () -> +com.samskivert.Log -> com.a.a: + com.samskivert.util.Logger log -> a + void () -> +com.samskivert.swing.DimenInfo -> com.a.a.a: + int count -> a + int totwid -> b + int tothei -> c + int maxwid -> d + int maxhei -> e + int numfix -> f + int fixwid -> g + int fixhei -> h + int maxfreewid -> i + int maxfreehei -> j + int totweight -> k + java.awt.Dimension[] dimens -> l + void () -> + java.lang.String toString() -> toString + boolean equals(java.lang.Object,java.lang.Object) -> a +com.samskivert.swing.GroupLayout -> com.a.a.b: + com.samskivert.swing.GroupLayout$Constraints FIXED -> a + com.samskivert.swing.GroupLayout$Policy NONE -> b + com.samskivert.swing.GroupLayout$Policy STRETCH -> c + com.samskivert.swing.GroupLayout$Policy EQUALIZE -> d + com.samskivert.swing.GroupLayout$Policy CONSTRAIN -> e + com.samskivert.swing.GroupLayout$Justification CENTER -> f + com.samskivert.swing.GroupLayout$Justification LEFT -> g + com.samskivert.swing.GroupLayout$Justification RIGHT -> h + com.samskivert.swing.GroupLayout$Justification TOP -> i + com.samskivert.swing.GroupLayout$Justification BOTTOM -> j + com.samskivert.swing.GroupLayout$Policy _policy -> k + com.samskivert.swing.GroupLayout$Policy _offpolicy -> l + int _gap -> m + com.samskivert.swing.GroupLayout$Justification _justification -> n + com.samskivert.swing.GroupLayout$Justification _offjust -> o + java.util.HashMap _constraints -> p + com.samskivert.swing.GroupLayout$Constraints DEFAULT_CONSTRAINTS -> q + void () -> + void addLayoutComponent(java.lang.String,java.awt.Component) -> addLayoutComponent + void removeLayoutComponent(java.awt.Component) -> removeLayoutComponent + void addLayoutComponent(java.awt.Component,java.lang.Object) -> addLayoutComponent + float getLayoutAlignmentX(java.awt.Container) -> getLayoutAlignmentX + float getLayoutAlignmentY(java.awt.Container) -> getLayoutAlignmentY + java.awt.Dimension minimumLayoutSize(java.awt.Container) -> minimumLayoutSize + java.awt.Dimension preferredLayoutSize(java.awt.Container) -> preferredLayoutSize + java.awt.Dimension maximumLayoutSize(java.awt.Container) -> maximumLayoutSize + java.awt.Dimension getLayoutSize(java.awt.Container,int) -> a + void invalidateLayout(java.awt.Container) -> invalidateLayout + com.samskivert.swing.GroupLayout$Constraints getConstraints(java.awt.Component) -> a + com.samskivert.swing.DimenInfo computeDimens(java.awt.Container,int) -> b + javax.swing.JPanel makeButtonBox(com.samskivert.swing.GroupLayout$Justification,java.awt.Component[]) -> a + void () -> +com.samskivert.swing.GroupLayout$Constraints -> com.a.a.b$a: + int _weight -> a + void (int) -> + boolean isFixed() -> a + int getWeight() -> b +com.samskivert.swing.GroupLayout$Justification -> com.a.a.b$b: + void () -> +com.samskivert.swing.GroupLayout$Policy -> com.a.a.b$c: + void () -> +com.samskivert.swing.HGroupLayout -> com.a.a.c: + void (com.samskivert.swing.GroupLayout$Policy,com.samskivert.swing.GroupLayout$Justification) -> + void () -> + java.awt.Dimension getLayoutSize(java.awt.Container,int) -> a + void layoutContainer(java.awt.Container) -> layoutContainer +com.samskivert.swing.Label -> com.a.a.d: + java.util.regex.Pattern COLOR_PATTERN -> a + java.lang.String _text -> b + java.lang.String _rawText -> c + int _style -> d + int _align -> e + java.awt.Dimension _constraints -> f + java.awt.Dimension _size -> g + float[] _leaders -> h + java.awt.Font _font -> i + java.awt.font.TextLayout[] _layouts -> j + java.awt.geom.Rectangle2D[] _lbounds -> k + java.awt.Color _alternateColor -> l + java.awt.Color _textColor -> m + boolean _mainDraw -> n + java.util.regex.Pattern ESCAPED_PATTERN -> o + java.lang.String unescapeColors(java.lang.String,boolean) -> a + void () -> + void (java.lang.String) -> + void (java.lang.String,java.awt.Color,java.awt.Font) -> + void (java.lang.String,int,java.awt.Color,java.awt.Color,java.awt.Font) -> + void setAlternateColor(java.awt.Color) -> a + void setStyle(int) -> a + void setTargetWidth(int) -> b + java.awt.Dimension getSize() -> a + void layout(java.awt.Graphics2D) -> a + java.util.List computeLines(java.awt.font.LineBreakMeasurer,int,java.awt.Dimension,boolean) -> a + void render(java.awt.Graphics2D,float,float) -> a + java.text.AttributedCharacterIterator textIterator(java.awt.Graphics2D) -> b + void addAttributes(java.text.AttributedString) -> a + double getWidth(java.awt.geom.Rectangle2D) -> a + java.awt.geom.Rectangle2D getBounds(java.awt.font.TextLayout) -> a + float getHeight(java.awt.font.TextLayout) -> b + void () -> +com.samskivert.swing.Spacer -> com.a.a.e: + void (int,int) -> + void (java.awt.Dimension) -> +com.samskivert.swing.VGroupLayout -> com.a.a.f: + void () -> + java.awt.Dimension getLayoutSize(java.awt.Container,int) -> a + void layoutContainer(java.awt.Container) -> layoutContainer +com.samskivert.swing.util.SwingUtil -> com.a.a.a.a: + boolean _defaultTextAntialiasing -> a + void centerWindow(java.awt.Window) -> a + java.lang.Object activateAntiAliasing(java.awt.Graphics2D) -> a + void restoreAntiAliasing(java.awt.Graphics2D,java.lang.Object) -> a + boolean getDefaultTextAntialiasing() -> a + void () -> +com.samskivert.util.AbstractIntSet -> com.a.b.a: + void () -> + boolean contains(int) -> a + int size() -> size + boolean isEmpty() -> isEmpty + boolean remove(int) -> b + java.util.Iterator iterator() -> iterator + boolean contains(java.lang.Object) -> contains + boolean remove(java.lang.Object) -> remove + boolean equals(java.lang.Object) -> equals + int hashCode() -> hashCode + java.lang.String toString() -> toString + boolean containsAll(java.util.Collection) -> containsAll + boolean addAll(java.util.Collection) -> addAll + boolean removeAll(java.util.Collection) -> removeAll + boolean retainAll(java.util.Collection) -> retainAll + boolean add(java.lang.Object) -> add +com.samskivert.util.AbstractInterator -> com.a.b.b: + void () -> + void remove() -> remove + java.lang.Object next() -> next +com.samskivert.util.ArrayUtil -> com.a.b.c: + java.lang.String safeToString(java.lang.Object) -> a + void () -> +com.samskivert.util.FormatterUtil -> com.a.b.d: + java.lang.String LINE_SEPARATOR -> a + void configureDefaultHandler(java.util.logging.Formatter) -> a + void () -> +com.samskivert.util.HashIntMap -> com.a.b.e: + com.samskivert.util.HashIntMap$Record[] _buckets -> a + int _size -> b + float _loadFactor -> c + com.samskivert.util.IntSet _keySet -> d + void (int,float) -> + void () -> + int size() -> size + boolean containsKey(java.lang.Object) -> containsKey + boolean containsKey(int) -> a + boolean containsValue(java.lang.Object) -> containsValue + java.lang.Object get(java.lang.Object) -> get + java.lang.Object put(int,java.lang.Object) -> a + java.lang.Object remove(java.lang.Object) -> remove + com.samskivert.util.HashIntMap$Record getImpl(int) -> b + com.samskivert.util.HashIntMap$Record removeImpl(int,boolean) -> a + void clear() -> clear + void ensureCapacity(int) -> c + int keyToIndex(int) -> d + void checkShrink() -> a + void resizeBuckets(int) -> e + java.util.Set entrySet() -> entrySet + java.util.Set keySet() -> keySet + com.samskivert.util.HashIntMap clone() -> b + com.samskivert.util.HashIntMap$Record[] createBuckets(int) -> f + java.lang.Object clone() -> clone + java.lang.Object put(java.lang.Object,java.lang.Object) -> put +com.samskivert.util.HashIntMap$1 -> com.a.b.f: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + int size() -> size + java.util.Iterator iterator() -> iterator +com.samskivert.util.HashIntMap$2 -> com.a.b.g: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + int size() -> size + java.util.Iterator iterator() -> iterator +com.samskivert.util.HashIntMap$3 -> com.a.b.h: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + com.samskivert.util.Interator interator() -> a + int size() -> size + boolean contains(int) -> a + boolean remove(int) -> b +com.samskivert.util.HashIntMap$3$1 -> com.a.b.i: + java.util.Iterator i -> a + com.samskivert.util.HashIntMap$3 this$1 -> b + void (com.samskivert.util.HashIntMap$3) -> + boolean hasNext() -> hasNext + int nextInt() -> a + void remove() -> remove +com.samskivert.util.HashIntMap$IntEntryIterator -> com.a.b.e$a: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + java.lang.Object next() -> next +com.samskivert.util.HashIntMap$MapEntryIterator -> com.a.b.e$b: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + java.lang.Object next() -> next +com.samskivert.util.HashIntMap$Record -> com.a.b.e$c: + com.samskivert.util.HashIntMap$Record next -> a + int key -> b + java.lang.Object value -> c + void (int,java.lang.Object) -> + int getIntKey() -> a + java.lang.Object getValue() -> getValue + java.lang.Object setValue(java.lang.Object) -> setValue + boolean equals(java.lang.Object) -> equals + int hashCode() -> hashCode + java.lang.String toString() -> toString + com.samskivert.util.HashIntMap$Record clone() -> b + java.lang.Object clone() -> clone + java.lang.Object getKey() -> getKey +com.samskivert.util.HashIntMap$RecordIterator -> com.a.b.e$d: + int _index -> a + com.samskivert.util.HashIntMap$Record _record -> b + com.samskivert.util.HashIntMap$Record _last -> c + com.samskivert.util.HashIntMap this$0 -> d + void (com.samskivert.util.HashIntMap) -> + boolean hasNext() -> hasNext + com.samskivert.util.HashIntMap$Record nextRecord() -> a + void remove() -> remove +com.samskivert.util.IntMap -> com.a.b.j: + java.lang.Object put(int,java.lang.Object) -> a +com.samskivert.util.IntMap$IntEntry -> com.a.b.j$a: + int getIntKey() -> a +com.samskivert.util.IntSet -> com.a.b.k: + boolean contains(int) -> a + com.samskivert.util.Interator interator() -> a +com.samskivert.util.Interable -> com.a.b.l: + com.samskivert.util.Interator interator() -> a +com.samskivert.util.Interator -> com.a.b.m: + int nextInt() -> a +com.samskivert.util.JDK14Logger -> com.a.b.n: + void () -> + void init() -> a + com.samskivert.util.Logger getLogger(java.lang.String) -> a +com.samskivert.util.JDK14Logger$Impl -> com.a.b.n$a: + java.util.logging.Logger _impl -> a + java.util.logging.Level[] LEVELS -> b + void (java.util.logging.Logger) -> + boolean shouldLog(int) -> a + void doLog(int,java.lang.String,java.lang.Throwable) -> a + void () -> +com.samskivert.util.Logger -> com.a.b.o: + com.samskivert.util.Logger$Factory _factory -> a + void () -> + com.samskivert.util.Logger getLogger(java.lang.String) -> a + void warning(java.lang.Object,java.lang.Object[]) -> a + boolean shouldLog(int) -> a + void doLog(int,java.lang.String,java.lang.Throwable) -> a + com.samskivert.util.Logger$Factory createConfiguredFactory() -> a + void () -> +com.samskivert.util.Logger$Factory -> com.a.b.o$a: + void init() -> a + com.samskivert.util.Logger getLogger(java.lang.String) -> a +com.samskivert.util.OneLineLogFormatter -> com.a.b.p: + boolean _showWhere -> a + java.util.Date _date -> b + java.text.SimpleDateFormat _format -> c + java.text.FieldPosition _fpos -> d + void () -> + void (boolean) -> + java.lang.String format(java.util.logging.LogRecord) -> format + void configureDefaultHandler() -> a +com.samskivert.util.RunAnywhere -> com.a.b.q: + boolean _isMacOS -> a + boolean isMacOS() -> a + void () -> +com.samskivert.util.StringUtil -> com.a.b.r: + java.text.NumberFormat _ffmt -> a + com.samskivert.util.IntMap _letterToBits -> b + boolean isBlank(java.lang.String) -> a + java.lang.String toString(java.lang.Object) -> a + void toString(java.lang.StringBuilder,java.lang.Object,java.lang.String,java.lang.String) -> a + void toString(java.lang.StringBuilder,java.lang.Object,java.lang.String,java.lang.String,java.lang.String) -> a + void coordsToString(java.lang.StringBuilder,int,int) -> a + void () -> +com.samskivert.util.Throttle -> com.a.b.s: + long[] _ops -> a + int _lastOp -> b + long _period -> c + void (int,long) -> + boolean throttleOp() -> a + java.lang.String toString() -> toString +com.samskivert.util.Tuple -> com.a.b.t: + java.lang.Object left -> a + java.lang.Object right -> b + void (java.lang.Object,java.lang.Object) -> + int hashCode() -> hashCode + boolean equals(java.lang.Object) -> equals + java.lang.String toString() -> toString +com.threerings.getdown.Log -> com.threerings.getdown.Log: + com.threerings.getdown.Log$Shim log -> log + java.lang.String DATE_FORMAT -> DATE_FORMAT + java.util.logging.Level[] LEVELS -> LEVELS + void () -> + java.lang.String format(java.lang.Object,java.lang.Object[]) -> format + void () -> +com.threerings.getdown.Log$OneLineFormatter -> com.threerings.getdown.Log$OneLineFormatter: + java.util.Date _date -> _date + java.text.SimpleDateFormat _format -> _format + java.text.FieldPosition _fpos -> _fpos + void () -> + java.lang.String format(java.util.logging.LogRecord) -> format +com.threerings.getdown.Log$Shim -> com.threerings.getdown.Log$Shim: + java.util.logging.Logger _impl -> _impl + void () -> + void debug(java.lang.Object,java.lang.Object[]) -> debug + void info(java.lang.Object,java.lang.Object[]) -> info + void warning(java.lang.Object,java.lang.Object[]) -> warning + void error(java.lang.Object,java.lang.Object[]) -> error + void doLog(int,java.lang.Object,java.lang.Object[]) -> doLog +com.threerings.getdown.cache.GarbageCollector -> com.threerings.getdown.cache.GarbageCollector: + void () -> + void collect(java.io.File,long) -> collect + void collectNative(java.io.File,long) -> collectNative + boolean shouldDelete(java.io.File,long) -> shouldDelete + java.io.File getLastAccessedFile(java.io.File) -> getLastAccessedFile + boolean isLastAccessedFile(java.io.File) -> isLastAccessedFile + java.io.File getCachedFile(java.io.File) -> getCachedFile + java.io.File access$000(java.io.File) -> access$000 + java.io.File access$100(java.io.File) -> access$100 + boolean access$200(java.io.File,long) -> access$200 +com.threerings.getdown.cache.GarbageCollector$1 -> com.threerings.getdown.cache.a: + long val$retentionPeriodMillis -> a + void (long) -> + void visit(java.io.File) -> visit +com.threerings.getdown.cache.ResourceCache -> com.threerings.getdown.cache.ResourceCache: + java.io.File _cacheDir -> _cacheDir + java.lang.String LAST_ACCESSED_FILE_SUFFIX -> LAST_ACCESSED_FILE_SUFFIX + void (java.io.File) -> + void createDirectoryIfNecessary(java.io.File) -> createDirectoryIfNecessary + java.io.File cacheFile(java.io.File,java.lang.String,java.lang.String) -> cacheFile + void createNewFile(java.io.File) -> createNewFile + java.lang.String getFileSuffix(java.io.File) -> getFileSuffix +com.threerings.getdown.data.Application -> com.threerings.getdown.data.Application: + java.lang.String CONFIG_FILE -> CONFIG_FILE + java.lang.String VERSION_FILE -> VERSION_FILE + java.lang.String PROP_PASSTHROUGH_PREFIX -> PROP_PASSTHROUGH_PREFIX + java.lang.String SIGNATURE_SUFFIX -> SIGNATURE_SUFFIX + java.lang.String MANIFEST_CLASS -> MANIFEST_CLASS + java.net.Proxy proxy -> proxy + com.threerings.getdown.data.EnvConfig _envc -> _envc + java.io.File _config -> _config + com.threerings.getdown.data.Digest _digest -> _digest + long _version -> _version + long _targetVersion -> _targetVersion + java.lang.String _appbase -> _appbase + java.net.URL _vappbase -> _vappbase + java.net.URL _latest -> _latest + java.lang.String _class -> _class + java.lang.String _dockName -> _dockName + java.lang.String _dockIconPath -> _dockIconPath + boolean _strictComments -> _strictComments + boolean _windebug -> _windebug + boolean _allowOffline -> _allowOffline + int _maxConcDownloads -> _maxConcDownloads + java.lang.String _trackingURL -> _trackingURL + java.util.Set _trackingPcts -> _trackingPcts + java.lang.String _trackingCookieName -> _trackingCookieName + java.lang.String _trackingCookieProperty -> _trackingCookieProperty + java.lang.String _trackingURLSuffix -> _trackingURLSuffix + java.lang.String _trackingGAHash -> _trackingGAHash + long _trackingStart -> _trackingStart + int _trackingId -> _trackingId + java.lang.String _javaVersionProp -> _javaVersionProp + java.lang.String _javaVersionRegex -> _javaVersionRegex + long _javaMinVersion -> _javaMinVersion + long _javaMaxVersion -> _javaMaxVersion + boolean _javaExactVersionRequired -> _javaExactVersionRequired + java.lang.String _javaLocation -> _javaLocation + java.util.List _codes -> _codes + java.util.List _resources -> _resources + boolean _useCodeCache -> _useCodeCache + int _codeCacheRetentionDays -> _codeCacheRetentionDays + java.util.Map _auxgroups -> _auxgroups + java.util.Map _auxactive -> _auxactive + java.util.List _jvmargs -> _jvmargs + java.util.List _appargs -> _appargs + java.lang.String[] _optimumJvmArgs -> _optimumJvmArgs + java.util.List _txtJvmArgs -> _txtJvmArgs + boolean _warnedAboutSetLastModified -> _warnedAboutSetLastModified + java.nio.channels.FileLock _lock -> _lock + java.nio.channels.FileChannel _lockChannel -> _lockChannel + java.util.Random _rando -> _rando + java.lang.String[] EMPTY_STRING_ARRAY -> EMPTY_STRING_ARRAY + java.lang.String ENV_VAR_PREFIX -> ENV_VAR_PREFIX + java.util.regex.Pattern ENV_VAR_PATTERN -> ENV_VAR_PATTERN + void (com.threerings.getdown.data.EnvConfig) -> + java.io.File getAppDir() -> getAppDir + boolean useCodeCache() -> useCodeCache + int getCodeCacheRetentionDays() -> getCodeCacheRetentionDays + int maxConcurrentDownloads() -> maxConcurrentDownloads + com.threerings.getdown.data.Resource getConfigResource() -> getConfigResource + java.util.List getCodeResources() -> getCodeResources + java.util.List getResources() -> getResources + java.lang.String getDigest(com.threerings.getdown.data.Resource) -> getDigest + java.util.List getAllActiveResources() -> getAllActiveResources + com.threerings.getdown.data.Application$AuxGroup getAuxGroup(java.lang.String) -> getAuxGroup + java.lang.Iterable getAuxGroups() -> getAuxGroups + boolean isAuxGroupActive(java.lang.String) -> isAuxGroupActive + java.util.List getActiveCodeResources() -> getActiveCodeResources + java.util.List getNativeResources() -> getNativeResources + java.util.List getActiveResources() -> getActiveResources + com.threerings.getdown.data.Resource getPatchResource(java.lang.String) -> getPatchResource + com.threerings.getdown.data.Resource getJavaVMResource() -> getJavaVMResource + com.threerings.getdown.data.Resource getFullResource() -> getFullResource + java.net.URL getTrackingURL(java.lang.String) -> getTrackingURL + java.net.URL getTrackingProgressURL(int) -> getTrackingProgressURL + java.lang.String getTrackingCookieName() -> getTrackingCookieName + java.lang.String getTrackingCookieProperty() -> getTrackingCookieProperty + com.threerings.getdown.util.Config init(boolean) -> init + void fillAssignmentListFromPairs(java.lang.String,java.util.List) -> fillAssignmentListFromPairs + java.net.URL getRemoteURL(java.lang.String) -> getRemoteURL + java.io.File getLocalPath(java.lang.String) -> getLocalPath + boolean haveValidJavaVersion() -> haveValidJavaVersion + boolean hasOptimumJvmArgs() -> hasOptimumJvmArgs + boolean allowOffline() -> allowOffline + void attemptRecovery(com.threerings.getdown.data.Application$StatusDisplay) -> attemptRecovery + void updateMetadata() -> updateMetadata + java.lang.Process createProcess(boolean) -> createProcess + java.lang.String[] createEnvironment() -> createEnvironment + void invokeDirect() -> invokeDirect + java.lang.String processArg(java.lang.String) -> processArg + boolean verifyMetadata(com.threerings.getdown.data.Application$StatusDisplay) -> verifyMetadata + void verifyResources(com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) -> verifyResources + void verifyResource(com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) -> verifyResource + void unpackResources(com.threerings.getdown.util.ProgressObserver,java.util.Set) -> unpackResources + void clearValidationMarkers() -> clearValidationMarkers + long getVersion() -> getVersion + java.net.URL createVAppBase(long) -> createVAppBase + void clearValidationMarkers(java.util.Iterator) -> clearValidationMarkers + void downloadConfigFile() -> downloadConfigFile + boolean lockForUpdates() -> lockForUpdates + void releaseLock() -> releaseLock + void downloadDigestFiles() -> downloadDigestFiles + void downloadControlFile(java.lang.String,int) -> downloadControlFile + java.io.File downloadFile(java.lang.String) -> downloadFile + com.threerings.getdown.data.Resource createResource(java.lang.String,java.util.EnumSet) -> createResource + void addAll(java.lang.String[],java.util.List) -> addAll + java.util.List intsToList(int[]) -> intsToList + java.util.List stringsToList(java.lang.String[]) -> stringsToList + void parseResources(com.threerings.getdown.util.Config,java.lang.String,java.util.EnumSet,java.util.List) -> parseResources + java.lang.String getGATrackingCode() -> getGATrackingCode + java.lang.String encodePath(java.lang.String) -> encodePath + java.io.File getLocalPath(java.io.File,java.lang.String) -> getLocalPath + void access$000(com.threerings.getdown.data.Application,com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) -> access$000 + void () -> +com.threerings.getdown.data.Application$1 -> com.threerings.getdown.data.a: + com.threerings.getdown.data.Application this$0 -> a + void (com.threerings.getdown.data.Application,java.net.URL[],java.lang.ClassLoader) -> + java.security.PermissionCollection getPermissions(java.security.CodeSource) -> getPermissions +com.threerings.getdown.data.Application$2 -> com.threerings.getdown.data.b: + java.util.concurrent.BlockingQueue val$actions -> b + com.threerings.getdown.util.ProgressObserver val$fobs -> a + com.threerings.getdown.data.Application this$0 -> c + void (com.threerings.getdown.data.Application,java.util.concurrent.BlockingQueue,com.threerings.getdown.util.ProgressObserver) -> + void progress(int) -> progress +com.threerings.getdown.data.Application$2$1 -> com.threerings.getdown.data.c: + int val$percent -> a + com.threerings.getdown.data.Application$2 this$1 -> b + void (com.threerings.getdown.data.Application$2,int) -> + void run() -> run +com.threerings.getdown.data.Application$3 -> com.threerings.getdown.data.d: + com.threerings.getdown.data.Resource val$rsrc -> b + com.threerings.getdown.util.ProgressAggregator val$pagg -> c + int val$index -> d + int[] val$fAlreadyValid -> e + java.util.Set val$unpackedAsync -> f + java.util.Set val$toInstallAsync -> g + java.util.Set val$toDownloadAsync -> h + java.util.concurrent.BlockingQueue val$actions -> i + int[] val$completed -> a + com.threerings.getdown.data.Application this$0 -> j + void (com.threerings.getdown.data.Application,com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressAggregator,int,int[],java.util.Set,java.util.Set,java.util.Set,java.util.concurrent.BlockingQueue,int[]) -> + void run() -> run +com.threerings.getdown.data.Application$3$1 -> com.threerings.getdown.data.e: + com.threerings.getdown.data.Application$3 this$1 -> a + void (com.threerings.getdown.data.Application$3) -> + void run() -> run +com.threerings.getdown.data.Application$AuxGroup -> com.threerings.getdown.data.Application$AuxGroup: + java.lang.String name -> name + java.util.List codes -> codes + java.util.List rsrcs -> rsrcs + void (java.lang.String,java.util.List,java.util.List) -> +com.threerings.getdown.data.Application$StatusDisplay -> com.threerings.getdown.data.Application$StatusDisplay: + void updateStatus(java.lang.String) -> updateStatus +com.threerings.getdown.data.Application$UpdateInterface -> com.threerings.getdown.data.Application$UpdateInterface: + java.lang.String name -> name + int background -> background + java.util.List rotatingBackgrounds -> rotatingBackgrounds + java.lang.String errorBackground -> errorBackground + java.util.List iconImages -> iconImages + java.lang.String backgroundImage -> backgroundImage + java.lang.String progressImage -> progressImage + com.threerings.getdown.util.Rectangle progress -> progress + int progressText -> progressText + int progressBar -> progressBar + com.threerings.getdown.util.Rectangle status -> status + int statusText -> statusText + int textShadow -> textShadow + java.lang.String installError -> installError + com.threerings.getdown.util.Rectangle patchNotes -> patchNotes + java.lang.String patchNotesUrl -> patchNotesUrl + boolean hideDecorations -> hideDecorations + boolean hideProgressText -> hideProgressText + int minShowSeconds -> minShowSeconds + java.util.Map stepPercentages -> stepPercentages + java.lang.String toString() -> toString + void (com.threerings.getdown.util.Config) -> +com.threerings.getdown.data.Application$UpdateInterface$Step -> com.threerings.getdown.data.Application$UpdateInterface$Step: + com.threerings.getdown.data.Application$UpdateInterface$Step UPDATE_JAVA -> UPDATE_JAVA + com.threerings.getdown.data.Application$UpdateInterface$Step VERIFY_METADATA -> VERIFY_METADATA + com.threerings.getdown.data.Application$UpdateInterface$Step DOWNLOAD -> DOWNLOAD + com.threerings.getdown.data.Application$UpdateInterface$Step PATCH -> PATCH + com.threerings.getdown.data.Application$UpdateInterface$Step VERIFY_RESOURCES -> VERIFY_RESOURCES + com.threerings.getdown.data.Application$UpdateInterface$Step REDOWNLOAD_RESOURCES -> REDOWNLOAD_RESOURCES + com.threerings.getdown.data.Application$UpdateInterface$Step UNPACK -> UNPACK + com.threerings.getdown.data.Application$UpdateInterface$Step LAUNCH -> LAUNCH + java.util.List defaultPercents -> defaultPercents + com.threerings.getdown.data.Application$UpdateInterface$Step[] $VALUES -> $VALUES + com.threerings.getdown.data.Application$UpdateInterface$Step[] values() -> values + com.threerings.getdown.data.Application$UpdateInterface$Step valueOf(java.lang.String) -> valueOf + void (java.lang.String,int,int[]) -> + void () -> +com.threerings.getdown.data.Build -> com.threerings.getdown.data.Build: + void () -> + java.lang.String time() -> time + java.lang.String version() -> version + java.util.List hostWhitelist() -> hostWhitelist +com.threerings.getdown.data.ClassPath -> com.threerings.getdown.data.ClassPath: + java.util.Set _classPathEntries -> _classPathEntries + void (java.util.LinkedHashSet) -> + java.lang.String asArgumentString() -> asArgumentString + java.net.URL[] asUrls() -> asUrls + java.util.Set getClassPathEntries() -> getClassPathEntries + java.net.URL getURL(java.io.File) -> getURL +com.threerings.getdown.data.Digest -> com.threerings.getdown.data.Digest: + int VERSION -> VERSION + java.util.HashMap _digests -> _digests + java.lang.String _metaDigest -> _metaDigest + java.lang.String FILE_NAME -> FILE_NAME + java.lang.String FILE_SUFFIX -> FILE_SUFFIX + java.lang.String digestFile(int) -> digestFile + java.lang.String sigAlgorithm(int) -> sigAlgorithm + void createDigest(int,java.util.List,java.io.File) -> createDigest + java.security.MessageDigest getMessageDigest(int) -> getMessageDigest + void (java.io.File,boolean) -> + void (java.io.File,int,boolean) -> + java.lang.String getMetaDigest() -> getMetaDigest + boolean validateResource(com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver) -> validateResource + java.lang.String getDigest(com.threerings.getdown.data.Resource) -> getDigest + void note(java.lang.StringBuilder,java.lang.String,java.lang.String) -> note +com.threerings.getdown.data.Digest$1 -> com.threerings.getdown.data.f: + int val$fversion -> a + java.util.Map val$digests -> b + com.threerings.getdown.data.Resource val$rsrc -> c + java.util.concurrent.BlockingQueue val$completed -> d + void (int,java.util.Map,com.threerings.getdown.data.Resource,java.util.concurrent.BlockingQueue) -> + void run() -> run +com.threerings.getdown.data.EnvConfig -> com.threerings.getdown.data.EnvConfig: + java.io.File appDir -> appDir + java.lang.String appId -> appId + java.lang.String appBase -> appBase + java.util.List certs -> certs + java.util.List appArgs -> appArgs + java.lang.String USER_HOME_KEY -> USER_HOME_KEY + com.threerings.getdown.data.EnvConfig create(java.lang.String[],java.util.List) -> create + void (java.io.File) -> + void (java.io.File,java.lang.String,java.lang.String,java.util.List,java.util.List) -> +com.threerings.getdown.data.EnvConfig$Note -> com.threerings.getdown.data.EnvConfig$Note: + com.threerings.getdown.data.EnvConfig$Note$Level level -> level + java.lang.String message -> message + com.threerings.getdown.data.EnvConfig$Note info(java.lang.String) -> info + com.threerings.getdown.data.EnvConfig$Note warn(java.lang.String) -> warn + com.threerings.getdown.data.EnvConfig$Note error(java.lang.String) -> error + void (com.threerings.getdown.data.EnvConfig$Note$Level,java.lang.String) -> +com.threerings.getdown.data.EnvConfig$Note$Level -> com.threerings.getdown.data.EnvConfig$Note$Level: + com.threerings.getdown.data.EnvConfig$Note$Level INFO -> INFO + com.threerings.getdown.data.EnvConfig$Note$Level WARN -> WARN + com.threerings.getdown.data.EnvConfig$Note$Level ERROR -> ERROR + com.threerings.getdown.data.EnvConfig$Note$Level[] $VALUES -> $VALUES + com.threerings.getdown.data.EnvConfig$Note$Level[] values() -> values + com.threerings.getdown.data.EnvConfig$Note$Level valueOf(java.lang.String) -> valueOf + void (java.lang.String,int) -> + void () -> +com.threerings.getdown.data.PathBuilder -> com.threerings.getdown.data.PathBuilder: + java.lang.String CODE_CACHE_DIR -> CODE_CACHE_DIR + java.lang.String NATIVE_CACHE_DIR -> NATIVE_CACHE_DIR + void () -> + com.threerings.getdown.data.ClassPath buildClassPath(com.threerings.getdown.data.Application) -> buildClassPath + com.threerings.getdown.data.ClassPath buildDefaultClassPath(com.threerings.getdown.data.Application) -> buildDefaultClassPath + com.threerings.getdown.data.ClassPath buildCachedClassPath(com.threerings.getdown.data.Application) -> buildCachedClassPath + com.threerings.getdown.data.ClassPath buildLibsPath(com.threerings.getdown.data.Application,boolean) -> buildLibsPath +com.threerings.getdown.data.Properties -> com.threerings.getdown.data.Properties: + java.lang.String GETDOWN -> GETDOWN + java.lang.String CONNECT_PORT -> CONNECT_PORT + void () -> +com.threerings.getdown.data.Resource -> com.threerings.getdown.data.Resource: + java.util.EnumSet NORMAL -> NORMAL + java.util.EnumSet UNPACK -> UNPACK + java.util.EnumSet EXEC -> EXEC + java.util.EnumSet PRELOAD -> PRELOAD + java.util.EnumSet NATIVE -> NATIVE + java.lang.String _path -> _path + java.net.URL _remote -> _remote + java.io.File _local -> _local + java.io.File _localNew -> _localNew + java.io.File _marker -> _marker + java.io.File _unpacked -> _unpacked + java.util.EnumSet _attrs -> _attrs + boolean _isJar -> _isJar + boolean _isPacked200Jar -> _isPacked200Jar + java.util.Comparator ENTRY_COMP -> ENTRY_COMP + int DIGEST_BUFFER_SIZE -> DIGEST_BUFFER_SIZE + java.lang.String computeDigest(int,java.io.File,java.security.MessageDigest,com.threerings.getdown.util.ProgressObserver) -> computeDigest + void (java.lang.String,java.net.URL,java.io.File,java.util.EnumSet) -> + java.lang.String getPath() -> getPath + java.io.File getLocal() -> getLocal + java.io.File getLocalNew() -> getLocalNew + java.io.File getUnpacked() -> getUnpacked + java.io.File getFinalTarget() -> getFinalTarget + java.net.URL getRemote() -> getRemote + boolean shouldUnpack() -> shouldUnpack + boolean shouldPredownload() -> shouldPredownload + boolean isNative() -> isNative + java.lang.String computeDigest(int,java.security.MessageDigest,com.threerings.getdown.util.ProgressObserver) -> computeDigest + boolean isMarkedValid() -> isMarkedValid + void markAsValid() -> markAsValid + void clearMarker() -> clearMarker + void install(boolean) -> install + void unpack() -> unpack + void applyAttrs() -> applyAttrs + void erase() -> erase + int compareTo(com.threerings.getdown.data.Resource) -> compareTo + boolean equals(java.lang.Object) -> equals + int hashCode() -> hashCode + java.lang.String toString() -> toString + void updateProgress(com.threerings.getdown.util.ProgressObserver,long,long) -> updateProgress + boolean isJar(java.lang.String) -> isJar + boolean isPacked200Jar(java.lang.String) -> isPacked200Jar + int compareTo(java.lang.Object) -> compareTo + void () -> +com.threerings.getdown.data.Resource$1 -> com.threerings.getdown.data.g: + void () -> + int compare(java.lang.Object,java.lang.Object) -> compare +com.threerings.getdown.data.Resource$Attr -> com.threerings.getdown.data.Resource$Attr: + com.threerings.getdown.data.Resource$Attr UNPACK -> UNPACK + com.threerings.getdown.data.Resource$Attr CLEAN -> CLEAN + com.threerings.getdown.data.Resource$Attr EXEC -> EXEC + com.threerings.getdown.data.Resource$Attr PRELOAD -> PRELOAD + com.threerings.getdown.data.Resource$Attr NATIVE -> NATIVE + com.threerings.getdown.data.Resource$Attr[] $VALUES -> $VALUES + com.threerings.getdown.data.Resource$Attr[] values() -> values + com.threerings.getdown.data.Resource$Attr valueOf(java.lang.String) -> valueOf + void (java.lang.String,int) -> + void () -> +com.threerings.getdown.data.SysProps -> com.threerings.getdown.data.SysProps: + void () -> + java.lang.String appDir() -> appDir + java.lang.String appId() -> appId + java.lang.String appBase() -> appBase + boolean noLogRedir() -> noLogRedir + java.lang.String appbaseDomain() -> appbaseDomain + java.lang.String appbaseOverride() -> appbaseOverride + boolean silent() -> silent + boolean launchInSilent() -> launchInSilent + boolean noUpdate() -> noUpdate + boolean noInstall() -> noInstall + int startDelay() -> startDelay + boolean noUnpack() -> noUnpack + boolean direct() -> direct + int connectTimeout() -> connectTimeout + int readTimeout() -> readTimeout + int threadPoolSize() -> threadPoolSize + long parseJavaVersion(java.lang.String,java.lang.String) -> parseJavaVersion + java.lang.String overrideAppbase(java.lang.String) -> overrideAppbase + java.lang.String replaceDomain(java.lang.String) -> replaceDomain +com.threerings.getdown.launcher.AbortPanel -> com.threerings.getdown.launcher.AbortPanel: + com.threerings.getdown.launcher.Getdown _getdown -> _getdown + java.util.ResourceBundle _msgs -> _msgs + void (com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) -> + java.awt.Dimension getPreferredSize() -> getPreferredSize + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed + java.lang.String get(java.lang.String) -> get +com.threerings.getdown.launcher.Getdown -> com.threerings.getdown.launcher.Getdown: + com.threerings.getdown.util.ProgressObserver _progobs -> _progobs + com.threerings.getdown.data.Application _app -> _app + com.threerings.getdown.data.Application$UpdateInterface _ifc -> _ifc + java.util.ResourceBundle _msgs -> _msgs + java.awt.Container _container -> _container + javax.swing.JLayeredPane _layers -> _layers + com.threerings.getdown.launcher.StatusPanel _status -> _status + javax.swing.JButton _patchNotes -> _patchNotes + com.threerings.getdown.launcher.AbortPanel _abort -> _abort + com.threerings.getdown.launcher.RotatingBackgrounds _background -> _background + boolean _dead -> _dead + boolean _silent -> _silent + boolean _launchInSilent -> _launchInSilent + boolean _noUpdate -> _noUpdate + long _startup -> _startup + java.util.Set _toInstallResources -> _toInstallResources + boolean _readyToInstall -> _readyToInstall + boolean _enableTracking -> _enableTracking + int _reportedProgress -> _reportedProgress + int _delay -> _delay + int _stepMaxPercent -> _stepMaxPercent + int _stepMinPercent -> _stepMinPercent + int _lastGlobalPercent -> _lastGlobalPercent + int _uiDisplayPercent -> _uiDisplayPercent + int MAX_LOOPS -> MAX_LOOPS + long FALLBACK_CHECK_TIME -> FALLBACK_CHECK_TIME + void (com.threerings.getdown.data.EnvConfig) -> + boolean isUpdateAvailable() -> isUpdateAvailable + void install() -> install + void run() -> run + void configProxy(java.lang.String,java.lang.String,java.lang.String,java.lang.String) -> configProxy + boolean detectProxy() -> detectProxy + void readConfig(boolean) -> readConfig + void doPredownloads(java.util.Collection) -> doPredownloads + void getdown() -> getdown + void updateStatus(java.lang.String) -> updateStatus + java.awt.image.BufferedImage loadImage(java.lang.String) -> loadImage + void updateJava() -> updateJava + void update() -> update + void download(java.util.Collection) -> download + void launch() -> launch + void createInterfaceAsync(boolean) -> createInterfaceAsync + void initInterface() -> initInterface + com.threerings.getdown.launcher.RotatingBackgrounds getBackground() -> getBackground + java.awt.Image getProgressImage() -> getProgressImage + void handleWindowClose() -> handleWindowClose + void fail(java.lang.String) -> fail + void setStep(com.threerings.getdown.data.Application$UpdateInterface$Step) -> setStep + int stepToGlobalPercent(int) -> stepToGlobalPercent + void setStatusAsync(java.lang.String,int,long,boolean) -> setStatusAsync + void reportTrackingEvent(java.lang.String,int) -> reportTrackingEvent + java.awt.Container createContainer() -> createContainer + void configureContainer() -> configureContainer + void showContainer() -> showContainer + void disposeContainer() -> disposeContainer + boolean invokeDirect() -> invokeDirect + void showDocument(java.lang.String) -> showDocument + void exit(int) -> exit + void copyStream(java.io.InputStream,java.io.PrintStream) -> copyStream + java.awt.Image loadImage(java.lang.String) -> loadImage +com.threerings.getdown.launcher.Getdown$1 -> com.threerings.getdown.launcher.a: + com.threerings.getdown.launcher.Getdown this$0 -> a + void (com.threerings.getdown.launcher.Getdown) -> + void run() -> run +com.threerings.getdown.launcher.Getdown$2 -> com.threerings.getdown.launcher.b: + int _lastCheck -> a + com.threerings.getdown.launcher.Getdown this$0 -> b + void (com.threerings.getdown.launcher.Getdown,java.net.Proxy) -> + void resolvingDownloads() -> resolvingDownloads + void downloadProgress(int,long) -> downloadProgress + void downloadFailed(com.threerings.getdown.data.Resource,java.lang.Exception) -> downloadFailed +com.threerings.getdown.launcher.Getdown$3 -> com.threerings.getdown.launcher.c: + java.io.InputStream val$stderr -> a + com.threerings.getdown.launcher.Getdown this$0 -> b + void (com.threerings.getdown.launcher.Getdown,java.io.InputStream) -> + void run() -> run +com.threerings.getdown.launcher.Getdown$4 -> com.threerings.getdown.launcher.d: + boolean val$reinit -> b + com.threerings.getdown.launcher.Getdown this$0 -> a + void (com.threerings.getdown.launcher.Getdown,boolean) -> + void run() -> run +com.threerings.getdown.launcher.Getdown$4$1 -> com.threerings.getdown.launcher.e: + com.threerings.getdown.launcher.Getdown$4 this$1 -> a + void (com.threerings.getdown.launcher.Getdown$4,java.lang.String) -> + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed +com.threerings.getdown.launcher.Getdown$5 -> com.threerings.getdown.launcher.f: + java.lang.String val$message -> a + int val$percent -> b + long val$remaining -> c + com.threerings.getdown.launcher.Getdown this$0 -> d + void (com.threerings.getdown.launcher.Getdown,java.lang.String,int,long) -> + void run() -> run +com.threerings.getdown.launcher.Getdown$6 -> com.threerings.getdown.launcher.g: + com.threerings.getdown.launcher.Getdown this$0 -> a + void (com.threerings.getdown.launcher.Getdown) -> + void progress(int) -> progress +com.threerings.getdown.launcher.Getdown$ProgressReporter -> com.threerings.getdown.launcher.Getdown$ProgressReporter: + java.net.URL _url -> _url + com.threerings.getdown.launcher.Getdown this$0 -> this$0 + void (com.threerings.getdown.launcher.Getdown,java.net.URL) -> + void run() -> run +com.threerings.getdown.launcher.GetdownApp -> com.threerings.getdown.launcher.GetdownApp: + void () -> + void main(java.lang.String[]) -> main + com.threerings.getdown.launcher.Getdown start(java.lang.String[]) -> start +com.threerings.getdown.launcher.GetdownApp$1 -> com.threerings.getdown.launcher.h: + javax.swing.JFrame _frame -> a + void (com.threerings.getdown.data.EnvConfig) -> + java.awt.Container createContainer() -> createContainer + void configureContainer() -> configureContainer + void showContainer() -> showContainer + void disposeContainer() -> disposeContainer + void showDocument(java.lang.String) -> showDocument + void exit(int) -> exit + void fail(java.lang.String) -> fail +com.threerings.getdown.launcher.GetdownApp$1$1 -> com.threerings.getdown.launcher.i: + com.threerings.getdown.launcher.GetdownApp$1 this$0 -> a + void (com.threerings.getdown.launcher.GetdownApp$1) -> + void windowClosing(java.awt.event.WindowEvent) -> windowClosing +com.threerings.getdown.launcher.GetdownApp$1$2 -> com.threerings.getdown.launcher.j: + com.threerings.getdown.launcher.GetdownApp$1 this$0 -> a + void (com.threerings.getdown.launcher.GetdownApp$1) -> + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed +com.threerings.getdown.launcher.GetdownApp$1$3 -> com.threerings.getdown.launcher.k: + com.threerings.getdown.launcher.GetdownApp$1 this$0 -> a + void (com.threerings.getdown.launcher.GetdownApp$1) -> + void run() -> run +com.threerings.getdown.launcher.GetdownApp$2 -> com.threerings.getdown.launcher.l: + int[] $SwitchMap$com$threerings$getdown$data$EnvConfig$Note$Level -> a + void () -> +com.threerings.getdown.launcher.MultipleGetdownRunning -> com.threerings.getdown.launcher.MultipleGetdownRunning: + void () -> +com.threerings.getdown.launcher.ProxyPanel -> com.threerings.getdown.launcher.ProxyPanel: + com.threerings.getdown.launcher.Getdown _getdown -> _getdown + java.util.ResourceBundle _msgs -> _msgs + javax.swing.JTextField _host -> _host + javax.swing.JTextField _port -> _port + javax.swing.JCheckBox _useAuth -> _useAuth + javax.swing.JTextField _username -> _username + javax.swing.JPasswordField _password -> _password + void (com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) -> + void setProxy(java.lang.String,java.lang.String) -> setProxy + void addNotify() -> addNotify + java.awt.Dimension getPreferredSize() -> getPreferredSize + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed + java.lang.String get(java.lang.String) -> get + java.awt.Dimension clampWidth(java.awt.Dimension,int) -> clampWidth +com.threerings.getdown.launcher.ProxyPanel$1 -> com.threerings.getdown.launcher.m: + com.threerings.getdown.launcher.ProxyPanel this$0 -> a + void (com.threerings.getdown.launcher.ProxyPanel) -> + void itemStateChanged(java.awt.event.ItemEvent) -> itemStateChanged +com.threerings.getdown.launcher.ProxyPanel$SaneLabelField -> com.threerings.getdown.launcher.ProxyPanel$SaneLabelField: + void (java.lang.String) -> + java.awt.Dimension getPreferredSize() -> getPreferredSize +com.threerings.getdown.launcher.ProxyPanel$SanePasswordField -> com.threerings.getdown.launcher.ProxyPanel$SanePasswordField: + void () -> + java.awt.Dimension getPreferredSize() -> getPreferredSize +com.threerings.getdown.launcher.ProxyPanel$SaneTextField -> com.threerings.getdown.launcher.ProxyPanel$SaneTextField: + void () -> + java.awt.Dimension getPreferredSize() -> getPreferredSize +com.threerings.getdown.launcher.ProxyUtil -> com.threerings.getdown.launcher.ProxyUtil: + java.lang.String PROXY_REGISTRY -> PROXY_REGISTRY + void () -> + boolean autoDetectProxy(com.threerings.getdown.data.Application) -> autoDetectProxy + boolean canLoadWithoutProxy(java.net.URL) -> canLoadWithoutProxy + void configProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String,java.lang.String,java.lang.String) -> configProxy + java.lang.String[] loadProxy(com.threerings.getdown.data.Application) -> loadProxy + void saveProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String) -> saveProxy + void initProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String,java.lang.String,java.lang.String) -> initProxy +com.threerings.getdown.launcher.ProxyUtil$1 -> com.threerings.getdown.launcher.n: + java.lang.String val$fuser -> a + char[] val$fpass -> b + void (java.lang.String,char[]) -> + java.net.PasswordAuthentication getPasswordAuthentication() -> getPasswordAuthentication +com.threerings.getdown.launcher.RotatingBackgrounds -> com.threerings.getdown.launcher.RotatingBackgrounds: + long currentDisplayStart -> currentDisplayStart + int current -> current + java.awt.Image[] images -> images + java.awt.Image errorImage -> errorImage + int[] percentages -> percentages + int[] minDisplayTime -> minDisplayTime + void () -> + void (java.awt.Image) -> + void (java.util.List,java.lang.String,com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader) -> + java.awt.Image getImage(int) -> getImage + java.awt.Image getErrorImage() -> getErrorImage + int getNumImages() -> getNumImages + void makeEmpty() -> makeEmpty +com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader -> com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader: + java.awt.Image loadImage(java.lang.String) -> loadImage +com.threerings.getdown.launcher.StatusPanel -> com.threerings.getdown.launcher.StatusPanel: + java.awt.Image _barimg -> _barimg + com.threerings.getdown.launcher.RotatingBackgrounds _bg -> _bg + java.awt.Dimension _psize -> _psize + java.util.ResourceBundle _msgs -> _msgs + int _progress -> _progress + java.lang.String _status -> _status + int _statusDots -> _statusDots + boolean _displayError -> _displayError + com.samskivert.swing.Label _label -> _label + com.samskivert.swing.Label _newlab -> _newlab + com.samskivert.swing.Label _plabel -> _plabel + com.samskivert.swing.Label _newplab -> _newplab + com.samskivert.swing.Label _rlabel -> _rlabel + com.samskivert.swing.Label _newrlab -> _newrlab + com.threerings.getdown.data.Application$UpdateInterface _ifc -> _ifc + javax.swing.Timer _timer -> _timer + long[] _remain -> _remain + int _ridx -> _ridx + com.samskivert.util.Throttle _rthrottle -> _rthrottle + java.awt.Font FONT -> FONT + void (java.util.ResourceBundle) -> + void init(com.threerings.getdown.data.Application$UpdateInterface,com.threerings.getdown.launcher.RotatingBackgrounds,java.awt.Image) -> init + boolean imageUpdate(java.awt.Image,int,int,int,int,int) -> imageUpdate + void setProgress(int,long) -> setProgress + void setStatus(java.lang.String,boolean) -> setStatus + void stopThrob() -> stopThrob + void addNotify() -> addNotify + void removeNotify() -> removeNotify + void paintComponent(java.awt.Graphics) -> paintComponent + java.awt.Dimension getPreferredSize() -> getPreferredSize + void updateStatusLabel() -> updateStatusLabel + int getStatusY(com.samskivert.swing.Label) -> getStatusY + com.samskivert.swing.Label createLabel(java.lang.String,java.awt.Color) -> createLabel + java.lang.String xlate(java.lang.String) -> xlate + java.lang.String get(java.lang.String,java.lang.String[]) -> get + java.lang.String get(java.lang.String) -> get + void () -> +com.threerings.getdown.launcher.StatusPanel$1 -> com.threerings.getdown.launcher.o: + com.threerings.getdown.launcher.StatusPanel this$0 -> a + void (com.threerings.getdown.launcher.StatusPanel) -> + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed +com.threerings.getdown.net.Downloader -> com.threerings.getdown.net.Downloader: + java.util.Map _sizes -> _sizes + java.util.Map _downloaded -> _downloaded + long _start -> _start + long _bytesPerSecond -> _bytesPerSecond + long _lastUpdate -> _lastUpdate + com.threerings.getdown.net.Downloader$State _state -> _state + long UPDATE_DELAY -> UPDATE_DELAY + void () -> + boolean download(java.util.Collection,int) -> download + void abort() -> abort + void resolvingDownloads() -> resolvingDownloads + void downloadProgress(int,long) -> downloadProgress + void downloadFailed(com.threerings.getdown.data.Resource,java.lang.Exception) -> downloadFailed + long checkSize(com.threerings.getdown.data.Resource) -> checkSize + void reportProgress(com.threerings.getdown.data.Resource,long,long) -> reportProgress + long sum(java.lang.Iterable) -> sum + void download(com.threerings.getdown.data.Resource) -> download +com.threerings.getdown.net.Downloader$1 -> com.threerings.getdown.net.a: + com.threerings.getdown.data.Resource val$rsrc -> a + com.threerings.getdown.net.Downloader this$0 -> b + void (com.threerings.getdown.net.Downloader,com.threerings.getdown.data.Resource) -> + void run() -> run +com.threerings.getdown.net.Downloader$State -> com.threerings.getdown.net.Downloader$State: + com.threerings.getdown.net.Downloader$State DOWNLOADING -> DOWNLOADING + com.threerings.getdown.net.Downloader$State COMPLETE -> COMPLETE + com.threerings.getdown.net.Downloader$State FAILED -> FAILED + com.threerings.getdown.net.Downloader$State ABORTED -> ABORTED + com.threerings.getdown.net.Downloader$State[] $VALUES -> $VALUES + com.threerings.getdown.net.Downloader$State[] values() -> values + com.threerings.getdown.net.Downloader$State valueOf(java.lang.String) -> valueOf + void (java.lang.String,int) -> + void () -> +com.threerings.getdown.net.HTTPDownloader -> com.threerings.getdown.net.HTTPDownloader: + java.net.Proxy _proxy -> _proxy + void (java.net.Proxy) -> + long checkSize(com.threerings.getdown.data.Resource) -> checkSize + void download(com.threerings.getdown.data.Resource) -> download +com.threerings.getdown.spi.ProxyAuth -> com.threerings.getdown.spi.ProxyAuth: + com.threerings.getdown.spi.ProxyAuth$Credentials loadCredentials(java.lang.String) -> loadCredentials + void saveCredentials(java.lang.String,java.lang.String,java.lang.String) -> saveCredentials +com.threerings.getdown.spi.ProxyAuth$Credentials -> com.threerings.getdown.spi.ProxyAuth$Credentials: + java.lang.String username -> username + java.lang.String password -> password + void (java.lang.String,java.lang.String) -> +com.threerings.getdown.tools.Differ -> com.threerings.getdown.tools.Differ: + void () -> + void createDiff(java.io.File,java.io.File,boolean) -> createDiff + void createPatch(java.io.File,java.util.ArrayList,java.util.ArrayList,boolean) -> createPatch + java.io.File rebuildJar(java.io.File) -> rebuildJar + void jarDiff(java.io.File,java.io.File,java.util.jar.JarOutputStream) -> jarDiff + void main(java.lang.String[]) -> main + void pipe(java.io.File,java.util.jar.JarOutputStream) -> pipe +com.threerings.getdown.tools.Digester -> com.threerings.getdown.tools.Digester: + void () -> + void main(java.lang.String[]) -> main + void createDigests(java.io.File,java.io.File,java.lang.String,java.lang.String) -> createDigests + void createDigest(int,java.io.File) -> createDigest + void signDigest(int,java.io.File,java.io.File,java.lang.String,java.lang.String) -> signDigest +com.threerings.getdown.tools.JarDiff -> com.threerings.getdown.tools.JarDiff: + int DEFAULT_READ_SIZE -> DEFAULT_READ_SIZE + byte[] newBytes -> newBytes + byte[] oldBytes -> oldBytes + boolean _debug -> _debug + void () -> + void createPatch(java.lang.String,java.lang.String,java.io.OutputStream,boolean) -> createPatch + void createIndex(java.util.jar.JarOutputStream,java.util.List,java.util.Map) -> createIndex + java.io.Writer writeEscapedString(java.io.Writer,java.lang.String) -> writeEscapedString + void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,com.threerings.getdown.tools.JarDiff$JarFile2) -> writeEntry + byte[] access$000() -> access$000 + byte[] access$100() -> access$100 + boolean access$200() -> access$200 + void () -> +com.threerings.getdown.tools.JarDiff$JarFile2 -> com.threerings.getdown.tools.JarDiff$a: + java.util.jar.JarFile _jar -> a + java.util.List _entries -> b + java.util.HashMap _nameToEntryMap -> c + java.util.HashMap _crcToEntryMap -> d + void (java.lang.String) -> + java.util.jar.JarFile getJarFile() -> a + java.util.Iterator iterator() -> iterator + java.util.jar.JarEntry getEntryByName(java.lang.String) -> a + boolean differs(java.io.InputStream,java.io.InputStream) -> a + boolean contains(com.threerings.getdown.tools.JarDiff$JarFile2,java.util.jar.JarEntry) -> a + java.lang.String hasSameContent(com.threerings.getdown.tools.JarDiff$JarFile2,java.util.jar.JarEntry) -> b + void index() -> b + void close() -> close +com.threerings.getdown.tools.JarDiffCodes -> com.threerings.getdown.tools.JarDiffCodes: + java.lang.String INDEX_NAME -> INDEX_NAME + java.lang.String VERSION_HEADER -> VERSION_HEADER + java.lang.String REMOVE_COMMAND -> REMOVE_COMMAND + java.lang.String MOVE_COMMAND -> MOVE_COMMAND +com.threerings.getdown.tools.JarDiffPatcher -> com.threerings.getdown.tools.JarDiffPatcher: + int DEFAULT_READ_SIZE -> DEFAULT_READ_SIZE + byte[] newBytes -> newBytes + byte[] oldBytes -> oldBytes + void () -> + void patchJar(java.lang.String,java.lang.String,java.io.File,com.threerings.getdown.util.ProgressObserver) -> patchJar + void updateObserver(com.threerings.getdown.util.ProgressObserver,double,double) -> updateObserver + void determineNameMapping(java.util.jar.JarFile,java.util.Set,java.util.Map) -> determineNameMapping + java.util.List getSubpaths(java.lang.String) -> getSubpaths + void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,java.util.jar.JarFile) -> writeEntry + void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,java.io.InputStream) -> writeEntry + void () -> +com.threerings.getdown.tools.Patcher -> com.threerings.getdown.tools.Patcher: + java.lang.String CREATE -> CREATE + java.lang.String PATCH -> PATCH + java.lang.String DELETE -> DELETE + com.threerings.getdown.util.ProgressObserver _obs -> _obs + long _complete -> _complete + long _plength -> _plength + byte[] _buffer -> _buffer + int COPY_BUFFER_SIZE -> COPY_BUFFER_SIZE + void () -> + void patch(java.io.File,java.io.File,com.threerings.getdown.util.ProgressObserver) -> patch + java.lang.String strip(java.lang.String,java.lang.String) -> strip + void createFile(java.util.jar.JarFile,java.util.zip.ZipEntry,java.io.File) -> createFile + void patchFile(java.util.jar.JarFile,java.util.zip.ZipEntry,java.io.File,java.lang.String) -> patchFile + void updateProgress(int) -> updateProgress + void main(java.lang.String[]) -> main +com.threerings.getdown.tools.Patcher$1 -> com.threerings.getdown.tools.a: + long val$elength -> a + com.threerings.getdown.tools.Patcher this$0 -> b + void (com.threerings.getdown.tools.Patcher,long) -> + void progress(int) -> progress +com.threerings.getdown.util.Base64 -> com.threerings.getdown.util.Base64: + int DEFAULT -> DEFAULT + int NO_PADDING -> NO_PADDING + int NO_WRAP -> NO_WRAP + int CRLF -> CRLF + int URL_SAFE -> URL_SAFE + int NO_CLOSE -> NO_CLOSE + boolean $assertionsDisabled -> $assertionsDisabled + byte[] decode(java.lang.String,int) -> decode + byte[] decode(byte[],int) -> decode + byte[] decode(byte[],int,int,int) -> decode + java.lang.String encodeToString(byte[],int) -> encodeToString + java.lang.String encodeToString(byte[],int,int,int) -> encodeToString + byte[] encode(byte[],int) -> encode + byte[] encode(byte[],int,int,int) -> encode + void () -> + void () -> +com.threerings.getdown.util.Base64$Coder -> com.threerings.getdown.util.Base64$a: + byte[] output -> a + int op -> b + void () -> +com.threerings.getdown.util.Base64$Decoder -> com.threerings.getdown.util.Base64$b: + int[] DECODE -> c + int[] DECODE_WEBSAFE -> d + int state -> e + int value -> f + int[] alphabet -> g + void (int,byte[]) -> + boolean process(byte[],int,int,boolean) -> a + void () -> +com.threerings.getdown.util.Base64$Encoder -> com.threerings.getdown.util.Base64$c: + byte[] ENCODE -> f + byte[] ENCODE_WEBSAFE -> g + byte[] tail -> h + int tailLen -> i + int count -> j + boolean do_padding -> c + boolean do_newline -> d + boolean do_cr -> e + byte[] alphabet -> k + boolean $assertionsDisabled -> l + void (int,byte[]) -> + boolean process(byte[],int,int,boolean) -> a + void () -> +com.threerings.getdown.util.Color -> com.threerings.getdown.util.Color: + int CLEAR -> CLEAR + int WHITE -> WHITE + int BLACK -> BLACK + float brightness(int) -> brightness + void () -> +com.threerings.getdown.util.Config -> com.threerings.getdown.util.Config: + com.threerings.getdown.util.Config EMPTY -> EMPTY + java.util.Map _data -> _data + com.threerings.getdown.util.Config$ParseOpts createOpts(boolean) -> createOpts + java.util.List parsePairs(java.io.File,com.threerings.getdown.util.Config$ParseOpts) -> parsePairs + java.util.List parsePairs(java.io.Reader,com.threerings.getdown.util.Config$ParseOpts) -> parsePairs + com.threerings.getdown.util.Rectangle parseRect(java.lang.String,java.lang.String) -> parseRect + java.lang.Integer parseColor(java.lang.String) -> parseColor + com.threerings.getdown.util.Config parseConfig(java.io.File,com.threerings.getdown.util.Config$ParseOpts) -> parseConfig + void (java.util.Map) -> + boolean hasValue(java.lang.String) -> hasValue + java.lang.Object getRaw(java.lang.String) -> getRaw + java.lang.String getString(java.lang.String) -> getString + java.lang.String getString(java.lang.String,java.lang.String) -> getString + boolean getBoolean(java.lang.String) -> getBoolean + java.lang.String[] getMultiValue(java.lang.String) -> getMultiValue + com.threerings.getdown.util.Rectangle getRect(java.lang.String,com.threerings.getdown.util.Rectangle) -> getRect + int getInt(java.lang.String,int) -> getInt + long getLong(java.lang.String,long) -> getLong + int getColor(java.lang.String,int) -> getColor + java.lang.String[] getList(java.lang.String) -> getList + java.lang.String getUrl(java.lang.String,java.lang.String) -> getUrl + boolean checkQualifiers(java.lang.String,java.lang.String,java.lang.String) -> checkQualifiers + boolean checkQualifier(java.lang.String,java.lang.String,java.lang.String) -> checkQualifier + void () -> +com.threerings.getdown.util.Config$ParseOpts -> com.threerings.getdown.util.Config$ParseOpts: + boolean biasToKey -> biasToKey + boolean strictComments -> strictComments + java.lang.String osname -> osname + java.lang.String osarch -> osarch + void () -> +com.threerings.getdown.util.ConnectionUtil -> com.threerings.getdown.util.ConnectionUtil: + void () -> + java.net.URLConnection open(java.net.Proxy,java.net.URL,int,int) -> open + java.net.HttpURLConnection openHttp(java.net.Proxy,java.net.URL,int,int) -> openHttp +com.threerings.getdown.util.FileUtil -> com.threerings.getdown.util.FileUtil: + void () -> + boolean renameTo(java.io.File,java.io.File) -> renameTo + boolean deleteHarder(java.io.File) -> deleteHarder + boolean deleteDirHarder(java.io.File) -> deleteDirHarder + java.util.List readLines(java.io.Reader) -> readLines + void unpackJar(java.util.jar.JarFile,java.io.File,boolean) -> unpackJar + void unpackPacked200Jar(java.io.File,java.io.File) -> unpackPacked200Jar + void copy(java.io.File,java.io.File) -> copy + void makeExecutable(java.io.File) -> makeExecutable + void walkTree(java.io.File,com.threerings.getdown.util.FileUtil$Visitor) -> walkTree +com.threerings.getdown.util.FileUtil$Visitor -> com.threerings.getdown.util.FileUtil$Visitor: + void visit(java.io.File) -> visit +com.threerings.getdown.util.HostWhitelist -> com.threerings.getdown.util.HostWhitelist: + void () -> + java.net.URL verify(java.net.URL) -> verify + java.net.URL verify(java.util.List,java.net.URL) -> verify +com.threerings.getdown.util.LaunchUtil -> com.threerings.getdown.util.LaunchUtil: + java.lang.String LOCAL_JAVA_DIR -> LOCAL_JAVA_DIR + boolean _isWindows -> _isWindows + boolean _isMacOS -> _isMacOS + boolean _isLinux -> _isLinux + void () -> + boolean updateVersionAndRelaunch(java.io.File,java.lang.String,java.lang.String) -> updateVersionAndRelaunch + java.lang.String getJVMPath(java.io.File) -> getJVMPath + java.lang.String getJVMPath(java.io.File,boolean) -> getJVMPath + void upgradeGetdown(java.io.File,java.io.File,java.io.File) -> upgradeGetdown + boolean mustMonitorChildren() -> mustMonitorChildren + boolean isWindows() -> isWindows + boolean isMacOS() -> isMacOS + boolean isLinux() -> isLinux + java.lang.String checkJVMPath(java.lang.String,boolean) -> checkJVMPath + void () -> +com.threerings.getdown.util.MessageUtil -> com.threerings.getdown.util.MessageUtil: + java.lang.String TAINT_CHAR -> TAINT_CHAR + void () -> + boolean isTainted(java.lang.String) -> isTainted + java.lang.String taint(java.lang.Object) -> taint + java.lang.String untaint(java.lang.String) -> untaint + java.lang.String compose(java.lang.String,java.lang.Object[]) -> compose + java.lang.String compose(java.lang.String,java.lang.String[]) -> compose + java.lang.String tcompose(java.lang.String,java.lang.Object[]) -> tcompose + java.lang.String tcompose(java.lang.String,java.lang.String[]) -> tcompose + java.lang.String escape(java.lang.String) -> escape + java.lang.String unescape(java.lang.String) -> unescape +com.threerings.getdown.util.ProgressAggregator -> com.threerings.getdown.util.ProgressAggregator: + com.threerings.getdown.util.ProgressObserver _target -> _target + long[] _sizes -> _sizes + int[] _progress -> _progress + void (com.threerings.getdown.util.ProgressObserver,long[]) -> + com.threerings.getdown.util.ProgressObserver startElement(int) -> startElement + void updateAggProgress() -> updateAggProgress + long sum(long[]) -> sum +com.threerings.getdown.util.ProgressAggregator$1 -> com.threerings.getdown.util.a: + int val$index -> a + com.threerings.getdown.util.ProgressAggregator this$0 -> b + void (com.threerings.getdown.util.ProgressAggregator,int) -> + void progress(int) -> progress +com.threerings.getdown.util.ProgressObserver -> com.threerings.getdown.util.ProgressObserver: + void progress(int) -> progress +com.threerings.getdown.util.Rectangle -> com.threerings.getdown.util.Rectangle: + int x -> x + int y -> y + int width -> width + int height -> height + void (int,int,int,int) -> + com.threerings.getdown.util.Rectangle union(com.threerings.getdown.util.Rectangle) -> union + java.lang.String toString() -> toString +com.threerings.getdown.util.StreamUtil -> com.threerings.getdown.util.StreamUtil: + void () -> + void close(java.io.InputStream) -> close + void close(java.io.OutputStream) -> close + void close(java.io.Reader) -> close + void close(java.io.Writer) -> close + java.io.OutputStream copy(java.io.InputStream,java.io.OutputStream) -> copy + byte[] toByteArray(java.io.InputStream) -> toByteArray +com.threerings.getdown.util.StringUtil -> com.threerings.getdown.util.StringUtil: + java.lang.String XLATE -> XLATE + void () -> + boolean couldBeValidUrl(java.lang.String) -> couldBeValidUrl + boolean isBlank(java.lang.String) -> isBlank + int[] parseIntArray(java.lang.String) -> parseIntArray + java.lang.String[] parseStringArray(java.lang.String) -> parseStringArray + java.lang.String[] parseStringArray(java.lang.String,boolean) -> parseStringArray + java.lang.String deNull(java.lang.String) -> deNull + java.lang.String hexlate(byte[],int) -> hexlate + java.lang.String hexlate(byte[]) -> hexlate + java.lang.String join(java.lang.Object[]) -> join + java.lang.String join(java.lang.Object[],boolean) -> join + java.lang.String join(java.lang.Object[],java.lang.String) -> join + java.lang.String join(java.lang.Object[],java.lang.String,boolean) -> join +com.threerings.getdown.util.VersionUtil -> com.threerings.getdown.util.VersionUtil: + void () -> + long readVersion(java.io.File) -> readVersion + void writeVersion(java.io.File,long) -> writeVersion + long parseJavaVersion(java.lang.String,java.lang.String) -> parseJavaVersion + long readReleaseVersion(java.io.File,java.lang.String) -> readReleaseVersion + int parseInt(java.lang.String) -> parseInt diff --git a/getdown/src/getdown/launcher/target/proguard_seed.txt b/getdown/src/getdown/launcher/target/proguard_seed.txt new file mode 100644 index 0000000..39b31be --- /dev/null +++ b/getdown/src/getdown/launcher/target/proguard_seed.txt @@ -0,0 +1,879 @@ +ca.beq.util.win32.registry.KeyIterator +ca.beq.util.win32.registry.KeyIterator: ca.beq.util.win32.registry.RegistryKey m_key +ca.beq.util.win32.registry.KeyIterator: int m_index +ca.beq.util.win32.registry.KeyIterator: int m_hkey +ca.beq.util.win32.registry.KeyIterator: int m_maxsize +ca.beq.util.win32.registry.KeyIterator: int m_count +ca.beq.util.win32.registry.KeyIterator: KeyIterator(ca.beq.util.win32.registry.RegistryKey) +ca.beq.util.win32.registry.KeyIterator: void initializeFields() +ca.beq.util.win32.registry.KeyIterator: boolean hasNext() +ca.beq.util.win32.registry.KeyIterator: java.lang.Object next() +ca.beq.util.win32.registry.KeyIterator: java.lang.String getNext() +ca.beq.util.win32.registry.KeyIterator: void remove() +ca.beq.util.win32.registry.RegistryException +ca.beq.util.win32.registry.RegistryException: RegistryException() +ca.beq.util.win32.registry.RegistryException: RegistryException(java.lang.String) +ca.beq.util.win32.registry.RegistryKey +ca.beq.util.win32.registry.RegistryKey: boolean c_initSucceeded +ca.beq.util.win32.registry.RegistryKey: ca.beq.util.win32.registry.RootKey m_root +ca.beq.util.win32.registry.RegistryKey: java.lang.String m_path +ca.beq.util.win32.registry.RegistryKey: void testInitialized() +ca.beq.util.win32.registry.RegistryKey: void initialize() +ca.beq.util.win32.registry.RegistryKey: void initialize(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: boolean isInitialized() +ca.beq.util.win32.registry.RegistryKey: void checkInitialized() +ca.beq.util.win32.registry.RegistryKey: RegistryKey() +ca.beq.util.win32.registry.RegistryKey: RegistryKey(ca.beq.util.win32.registry.RootKey) +ca.beq.util.win32.registry.RegistryKey: RegistryKey(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: RegistryKey(ca.beq.util.win32.registry.RootKey,java.lang.String) +ca.beq.util.win32.registry.RegistryKey: ca.beq.util.win32.registry.RootKey getRootKey() +ca.beq.util.win32.registry.RegistryKey: java.lang.String getPath() +ca.beq.util.win32.registry.RegistryKey: java.lang.String makePath(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: java.lang.String getName() +ca.beq.util.win32.registry.RegistryKey: boolean exists() +ca.beq.util.win32.registry.RegistryKey: void create() +ca.beq.util.win32.registry.RegistryKey: ca.beq.util.win32.registry.RegistryKey createSubkey(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: void delete() +ca.beq.util.win32.registry.RegistryKey: boolean hasSubkeys() +ca.beq.util.win32.registry.RegistryKey: boolean hasSubkey(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: java.util.Iterator subkeys() +ca.beq.util.win32.registry.RegistryKey: java.util.Iterator values() +ca.beq.util.win32.registry.RegistryKey: boolean hasValue(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: boolean hasValues() +ca.beq.util.win32.registry.RegistryKey: ca.beq.util.win32.registry.RegistryValue getValue(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: void setValue(ca.beq.util.win32.registry.RegistryValue) +ca.beq.util.win32.registry.RegistryKey: void deleteValue(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: java.lang.String toString() +ca.beq.util.win32.registry.RegistryKey: void () +ca.beq.util.win32.registry.RegistryValue +ca.beq.util.win32.registry.RegistryValue: java.lang.String m_name +ca.beq.util.win32.registry.RegistryValue: ca.beq.util.win32.registry.ValueType m_type +ca.beq.util.win32.registry.RegistryValue: java.lang.Object m_data +ca.beq.util.win32.registry.RegistryValue: RegistryValue() +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.Object) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,java.lang.Object) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,ca.beq.util.win32.registry.ValueType,java.lang.Object) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,boolean) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,byte) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,int) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,long) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,float) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,double) +ca.beq.util.win32.registry.RegistryValue: java.lang.String getName() +ca.beq.util.win32.registry.RegistryValue: void setName(java.lang.String) +ca.beq.util.win32.registry.RegistryValue: ca.beq.util.win32.registry.ValueType getType() +ca.beq.util.win32.registry.RegistryValue: void setType(ca.beq.util.win32.registry.ValueType) +ca.beq.util.win32.registry.RegistryValue: java.lang.Object getData() +ca.beq.util.win32.registry.RegistryValue: void setData(java.lang.Object) +ca.beq.util.win32.registry.RegistryValue: void setData(byte) +ca.beq.util.win32.registry.RegistryValue: void setData(boolean) +ca.beq.util.win32.registry.RegistryValue: void setData(int) +ca.beq.util.win32.registry.RegistryValue: void setData(long) +ca.beq.util.win32.registry.RegistryValue: void setData(float) +ca.beq.util.win32.registry.RegistryValue: void setData(double) +ca.beq.util.win32.registry.RegistryValue: java.lang.String getStringValue() +ca.beq.util.win32.registry.RegistryValue: java.lang.String toString() +ca.beq.util.win32.registry.RootKey +ca.beq.util.win32.registry.RootKey: java.lang.String m_name +ca.beq.util.win32.registry.RootKey: int m_value +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_CLASSES_ROOT +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_CURRENT_USER +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_LOCAL_MACHINE +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_USERS +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_CURRENT_CONFIG +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_PERFORMANCE_DATA +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_DYN_DATA +ca.beq.util.win32.registry.RootKey: RootKey(java.lang.String,int) +ca.beq.util.win32.registry.RootKey: int getValue() +ca.beq.util.win32.registry.RootKey: java.lang.String toString() +ca.beq.util.win32.registry.RootKey: void () +ca.beq.util.win32.registry.ValueIterator +ca.beq.util.win32.registry.ValueIterator: ca.beq.util.win32.registry.RegistryKey m_key +ca.beq.util.win32.registry.ValueIterator: int m_index +ca.beq.util.win32.registry.ValueIterator: int m_hkey +ca.beq.util.win32.registry.ValueIterator: int m_maxsize +ca.beq.util.win32.registry.ValueIterator: int m_count +ca.beq.util.win32.registry.ValueIterator: ValueIterator(ca.beq.util.win32.registry.RegistryKey) +ca.beq.util.win32.registry.ValueIterator: void initializeFields() +ca.beq.util.win32.registry.ValueIterator: boolean hasNext() +ca.beq.util.win32.registry.ValueIterator: java.lang.Object next() +ca.beq.util.win32.registry.ValueIterator: java.lang.String getNext() +ca.beq.util.win32.registry.ValueIterator: void remove() +ca.beq.util.win32.registry.ValueType +ca.beq.util.win32.registry.ValueType: java.lang.String m_name +ca.beq.util.win32.registry.ValueType: int m_value +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_NONE +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_SZ +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_EXPAND_SZ +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_BINARY +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_DWORD +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_DWORD_LITTLE_ENDIAN +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_DWORD_BIG_ENDIAN +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_MULTI_SZ +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_RESOURCE_LIST +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_LINK +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_FULL_RESOURCE_DESCRIPTOR +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_RESOURCE_REQUIREMENTS_LIST +ca.beq.util.win32.registry.ValueType: ValueType(java.lang.String,int) +ca.beq.util.win32.registry.ValueType: int getValue() +ca.beq.util.win32.registry.ValueType: java.lang.String toString() +ca.beq.util.win32.registry.ValueType: void () +com.threerings.getdown.Log +com.threerings.getdown.Log: com.threerings.getdown.Log$Shim log +com.threerings.getdown.Log: java.lang.String DATE_FORMAT +com.threerings.getdown.Log: java.util.logging.Level[] LEVELS +com.threerings.getdown.Log: Log() +com.threerings.getdown.Log: java.lang.String format(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log: void () +com.threerings.getdown.Log$OneLineFormatter +com.threerings.getdown.Log$OneLineFormatter: java.util.Date _date +com.threerings.getdown.Log$OneLineFormatter: java.text.SimpleDateFormat _format +com.threerings.getdown.Log$OneLineFormatter: java.text.FieldPosition _fpos +com.threerings.getdown.Log$OneLineFormatter: Log$OneLineFormatter() +com.threerings.getdown.Log$OneLineFormatter: java.lang.String format(java.util.logging.LogRecord) +com.threerings.getdown.Log$Shim +com.threerings.getdown.Log$Shim: java.util.logging.Logger _impl +com.threerings.getdown.Log$Shim: Log$Shim() +com.threerings.getdown.Log$Shim: void debug(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log$Shim: void info(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log$Shim: void warning(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log$Shim: void error(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log$Shim: void doLog(int,java.lang.Object,java.lang.Object[]) +com.threerings.getdown.cache.GarbageCollector +com.threerings.getdown.cache.GarbageCollector: GarbageCollector() +com.threerings.getdown.cache.GarbageCollector: void collect(java.io.File,long) +com.threerings.getdown.cache.GarbageCollector: void collectNative(java.io.File,long) +com.threerings.getdown.cache.GarbageCollector: boolean shouldDelete(java.io.File,long) +com.threerings.getdown.cache.GarbageCollector: java.io.File getLastAccessedFile(java.io.File) +com.threerings.getdown.cache.GarbageCollector: boolean isLastAccessedFile(java.io.File) +com.threerings.getdown.cache.GarbageCollector: java.io.File getCachedFile(java.io.File) +com.threerings.getdown.cache.GarbageCollector: java.io.File access$000(java.io.File) +com.threerings.getdown.cache.GarbageCollector: java.io.File access$100(java.io.File) +com.threerings.getdown.cache.GarbageCollector: boolean access$200(java.io.File,long) +com.threerings.getdown.cache.ResourceCache +com.threerings.getdown.cache.ResourceCache: java.io.File _cacheDir +com.threerings.getdown.cache.ResourceCache: java.lang.String LAST_ACCESSED_FILE_SUFFIX +com.threerings.getdown.cache.ResourceCache: ResourceCache(java.io.File) +com.threerings.getdown.cache.ResourceCache: void createDirectoryIfNecessary(java.io.File) +com.threerings.getdown.cache.ResourceCache: java.io.File cacheFile(java.io.File,java.lang.String,java.lang.String) +com.threerings.getdown.cache.ResourceCache: void createNewFile(java.io.File) +com.threerings.getdown.cache.ResourceCache: java.lang.String getFileSuffix(java.io.File) +com.threerings.getdown.data.Application +com.threerings.getdown.data.Application: java.lang.String CONFIG_FILE +com.threerings.getdown.data.Application: java.lang.String VERSION_FILE +com.threerings.getdown.data.Application: java.lang.String PROP_PASSTHROUGH_PREFIX +com.threerings.getdown.data.Application: java.lang.String SIGNATURE_SUFFIX +com.threerings.getdown.data.Application: java.lang.String MANIFEST_CLASS +com.threerings.getdown.data.Application: java.net.Proxy proxy +com.threerings.getdown.data.Application: com.threerings.getdown.data.EnvConfig _envc +com.threerings.getdown.data.Application: java.io.File _config +com.threerings.getdown.data.Application: com.threerings.getdown.data.Digest _digest +com.threerings.getdown.data.Application: long _version +com.threerings.getdown.data.Application: long _targetVersion +com.threerings.getdown.data.Application: java.lang.String _appbase +com.threerings.getdown.data.Application: java.net.URL _vappbase +com.threerings.getdown.data.Application: java.net.URL _latest +com.threerings.getdown.data.Application: java.lang.String _class +com.threerings.getdown.data.Application: java.lang.String _dockName +com.threerings.getdown.data.Application: java.lang.String _dockIconPath +com.threerings.getdown.data.Application: boolean _strictComments +com.threerings.getdown.data.Application: boolean _windebug +com.threerings.getdown.data.Application: boolean _allowOffline +com.threerings.getdown.data.Application: int _maxConcDownloads +com.threerings.getdown.data.Application: java.lang.String _trackingURL +com.threerings.getdown.data.Application: java.util.Set _trackingPcts +com.threerings.getdown.data.Application: java.lang.String _trackingCookieName +com.threerings.getdown.data.Application: java.lang.String _trackingCookieProperty +com.threerings.getdown.data.Application: java.lang.String _trackingURLSuffix +com.threerings.getdown.data.Application: java.lang.String _trackingGAHash +com.threerings.getdown.data.Application: long _trackingStart +com.threerings.getdown.data.Application: int _trackingId +com.threerings.getdown.data.Application: java.lang.String _javaVersionProp +com.threerings.getdown.data.Application: java.lang.String _javaVersionRegex +com.threerings.getdown.data.Application: long _javaMinVersion +com.threerings.getdown.data.Application: long _javaMaxVersion +com.threerings.getdown.data.Application: boolean _javaExactVersionRequired +com.threerings.getdown.data.Application: java.lang.String _javaLocation +com.threerings.getdown.data.Application: java.util.List _codes +com.threerings.getdown.data.Application: java.util.List _resources +com.threerings.getdown.data.Application: boolean _useCodeCache +com.threerings.getdown.data.Application: int _codeCacheRetentionDays +com.threerings.getdown.data.Application: java.util.Map _auxgroups +com.threerings.getdown.data.Application: java.util.Map _auxactive +com.threerings.getdown.data.Application: java.util.List _jvmargs +com.threerings.getdown.data.Application: java.util.List _appargs +com.threerings.getdown.data.Application: java.lang.String[] _optimumJvmArgs +com.threerings.getdown.data.Application: java.util.List _txtJvmArgs +com.threerings.getdown.data.Application: boolean _warnedAboutSetLastModified +com.threerings.getdown.data.Application: java.nio.channels.FileLock _lock +com.threerings.getdown.data.Application: java.nio.channels.FileChannel _lockChannel +com.threerings.getdown.data.Application: java.util.Random _rando +com.threerings.getdown.data.Application: java.lang.String[] EMPTY_STRING_ARRAY +com.threerings.getdown.data.Application: java.lang.String ENV_VAR_PREFIX +com.threerings.getdown.data.Application: java.util.regex.Pattern ENV_VAR_PATTERN +com.threerings.getdown.data.Application: Application(com.threerings.getdown.data.EnvConfig) +com.threerings.getdown.data.Application: java.io.File getAppDir() +com.threerings.getdown.data.Application: boolean useCodeCache() +com.threerings.getdown.data.Application: int getCodeCacheRetentionDays() +com.threerings.getdown.data.Application: int maxConcurrentDownloads() +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource getConfigResource() +com.threerings.getdown.data.Application: java.util.List getCodeResources() +com.threerings.getdown.data.Application: java.util.List getResources() +com.threerings.getdown.data.Application: java.lang.String getDigest(com.threerings.getdown.data.Resource) +com.threerings.getdown.data.Application: java.util.List getAllActiveResources() +com.threerings.getdown.data.Application: com.threerings.getdown.data.Application$AuxGroup getAuxGroup(java.lang.String) +com.threerings.getdown.data.Application: java.lang.Iterable getAuxGroups() +com.threerings.getdown.data.Application: boolean isAuxGroupActive(java.lang.String) +com.threerings.getdown.data.Application: java.util.List getActiveCodeResources() +com.threerings.getdown.data.Application: java.util.List getNativeResources() +com.threerings.getdown.data.Application: java.util.List getActiveResources() +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource getPatchResource(java.lang.String) +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource getJavaVMResource() +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource getFullResource() +com.threerings.getdown.data.Application: java.net.URL getTrackingURL(java.lang.String) +com.threerings.getdown.data.Application: java.net.URL getTrackingProgressURL(int) +com.threerings.getdown.data.Application: java.lang.String getTrackingCookieName() +com.threerings.getdown.data.Application: java.lang.String getTrackingCookieProperty() +com.threerings.getdown.data.Application: com.threerings.getdown.util.Config init(boolean) +com.threerings.getdown.data.Application: void fillAssignmentListFromPairs(java.lang.String,java.util.List) +com.threerings.getdown.data.Application: java.net.URL getRemoteURL(java.lang.String) +com.threerings.getdown.data.Application: java.io.File getLocalPath(java.lang.String) +com.threerings.getdown.data.Application: boolean haveValidJavaVersion() +com.threerings.getdown.data.Application: boolean hasOptimumJvmArgs() +com.threerings.getdown.data.Application: boolean allowOffline() +com.threerings.getdown.data.Application: void attemptRecovery(com.threerings.getdown.data.Application$StatusDisplay) +com.threerings.getdown.data.Application: void updateMetadata() +com.threerings.getdown.data.Application: java.lang.Process createProcess(boolean) +com.threerings.getdown.data.Application: java.lang.String[] createEnvironment() +com.threerings.getdown.data.Application: void invokeDirect() +com.threerings.getdown.data.Application: java.lang.String processArg(java.lang.String) +com.threerings.getdown.data.Application: boolean verifyMetadata(com.threerings.getdown.data.Application$StatusDisplay) +com.threerings.getdown.data.Application: void verifyResources(com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) +com.threerings.getdown.data.Application: void verifyResource(com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) +com.threerings.getdown.data.Application: void unpackResources(com.threerings.getdown.util.ProgressObserver,java.util.Set) +com.threerings.getdown.data.Application: void clearValidationMarkers() +com.threerings.getdown.data.Application: long getVersion() +com.threerings.getdown.data.Application: java.net.URL createVAppBase(long) +com.threerings.getdown.data.Application: void clearValidationMarkers(java.util.Iterator) +com.threerings.getdown.data.Application: void downloadConfigFile() +com.threerings.getdown.data.Application: boolean lockForUpdates() +com.threerings.getdown.data.Application: void releaseLock() +com.threerings.getdown.data.Application: void downloadDigestFiles() +com.threerings.getdown.data.Application: void downloadControlFile(java.lang.String,int) +com.threerings.getdown.data.Application: java.io.File downloadFile(java.lang.String) +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource createResource(java.lang.String,java.util.EnumSet) +com.threerings.getdown.data.Application: void addAll(java.lang.String[],java.util.List) +com.threerings.getdown.data.Application: java.util.List intsToList(int[]) +com.threerings.getdown.data.Application: java.util.List stringsToList(java.lang.String[]) +com.threerings.getdown.data.Application: void parseResources(com.threerings.getdown.util.Config,java.lang.String,java.util.EnumSet,java.util.List) +com.threerings.getdown.data.Application: java.lang.String getGATrackingCode() +com.threerings.getdown.data.Application: java.lang.String encodePath(java.lang.String) +com.threerings.getdown.data.Application: java.io.File getLocalPath(java.io.File,java.lang.String) +com.threerings.getdown.data.Application: void access$000(com.threerings.getdown.data.Application,com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) +com.threerings.getdown.data.Application: void () +com.threerings.getdown.data.Application$AuxGroup +com.threerings.getdown.data.Application$AuxGroup: java.lang.String name +com.threerings.getdown.data.Application$AuxGroup: java.util.List codes +com.threerings.getdown.data.Application$AuxGroup: java.util.List rsrcs +com.threerings.getdown.data.Application$AuxGroup: Application$AuxGroup(java.lang.String,java.util.List,java.util.List) +com.threerings.getdown.data.Application$StatusDisplay +com.threerings.getdown.data.Application$StatusDisplay: void updateStatus(java.lang.String) +com.threerings.getdown.data.Application$UpdateInterface +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String name +com.threerings.getdown.data.Application$UpdateInterface: int background +com.threerings.getdown.data.Application$UpdateInterface: java.util.List rotatingBackgrounds +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String errorBackground +com.threerings.getdown.data.Application$UpdateInterface: java.util.List iconImages +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String backgroundImage +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String progressImage +com.threerings.getdown.data.Application$UpdateInterface: com.threerings.getdown.util.Rectangle progress +com.threerings.getdown.data.Application$UpdateInterface: int progressText +com.threerings.getdown.data.Application$UpdateInterface: int progressBar +com.threerings.getdown.data.Application$UpdateInterface: com.threerings.getdown.util.Rectangle status +com.threerings.getdown.data.Application$UpdateInterface: int statusText +com.threerings.getdown.data.Application$UpdateInterface: int textShadow +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String installError +com.threerings.getdown.data.Application$UpdateInterface: com.threerings.getdown.util.Rectangle patchNotes +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String patchNotesUrl +com.threerings.getdown.data.Application$UpdateInterface: boolean hideDecorations +com.threerings.getdown.data.Application$UpdateInterface: boolean hideProgressText +com.threerings.getdown.data.Application$UpdateInterface: int minShowSeconds +com.threerings.getdown.data.Application$UpdateInterface: java.util.Map stepPercentages +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String toString() +com.threerings.getdown.data.Application$UpdateInterface: Application$UpdateInterface(com.threerings.getdown.util.Config) +com.threerings.getdown.data.Application$UpdateInterface$Step +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step UPDATE_JAVA +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step VERIFY_METADATA +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step DOWNLOAD +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step PATCH +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step VERIFY_RESOURCES +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step REDOWNLOAD_RESOURCES +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step UNPACK +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step LAUNCH +com.threerings.getdown.data.Application$UpdateInterface$Step: java.util.List defaultPercents +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step[] $VALUES +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step[] values() +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step valueOf(java.lang.String) +com.threerings.getdown.data.Application$UpdateInterface$Step: Application$UpdateInterface$Step(java.lang.String,int,int[]) +com.threerings.getdown.data.Application$UpdateInterface$Step: void () +com.threerings.getdown.data.Build +com.threerings.getdown.data.Build: Build() +com.threerings.getdown.data.Build: java.lang.String time() +com.threerings.getdown.data.Build: java.lang.String version() +com.threerings.getdown.data.Build: java.util.List hostWhitelist() +com.threerings.getdown.data.ClassPath +com.threerings.getdown.data.ClassPath: java.util.Set _classPathEntries +com.threerings.getdown.data.ClassPath: ClassPath(java.util.LinkedHashSet) +com.threerings.getdown.data.ClassPath: java.lang.String asArgumentString() +com.threerings.getdown.data.ClassPath: java.net.URL[] asUrls() +com.threerings.getdown.data.ClassPath: java.util.Set getClassPathEntries() +com.threerings.getdown.data.ClassPath: java.net.URL getURL(java.io.File) +com.threerings.getdown.data.Digest +com.threerings.getdown.data.Digest: int VERSION +com.threerings.getdown.data.Digest: java.util.HashMap _digests +com.threerings.getdown.data.Digest: java.lang.String _metaDigest +com.threerings.getdown.data.Digest: java.lang.String FILE_NAME +com.threerings.getdown.data.Digest: java.lang.String FILE_SUFFIX +com.threerings.getdown.data.Digest: java.lang.String digestFile(int) +com.threerings.getdown.data.Digest: java.lang.String sigAlgorithm(int) +com.threerings.getdown.data.Digest: void createDigest(int,java.util.List,java.io.File) +com.threerings.getdown.data.Digest: java.security.MessageDigest getMessageDigest(int) +com.threerings.getdown.data.Digest: Digest(java.io.File,boolean) +com.threerings.getdown.data.Digest: Digest(java.io.File,int,boolean) +com.threerings.getdown.data.Digest: java.lang.String getMetaDigest() +com.threerings.getdown.data.Digest: boolean validateResource(com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.data.Digest: java.lang.String getDigest(com.threerings.getdown.data.Resource) +com.threerings.getdown.data.Digest: void note(java.lang.StringBuilder,java.lang.String,java.lang.String) +com.threerings.getdown.data.EnvConfig +com.threerings.getdown.data.EnvConfig: java.io.File appDir +com.threerings.getdown.data.EnvConfig: java.lang.String appId +com.threerings.getdown.data.EnvConfig: java.lang.String appBase +com.threerings.getdown.data.EnvConfig: java.util.List certs +com.threerings.getdown.data.EnvConfig: java.util.List appArgs +com.threerings.getdown.data.EnvConfig: java.lang.String USER_HOME_KEY +com.threerings.getdown.data.EnvConfig: com.threerings.getdown.data.EnvConfig create(java.lang.String[],java.util.List) +com.threerings.getdown.data.EnvConfig: EnvConfig(java.io.File) +com.threerings.getdown.data.EnvConfig: EnvConfig(java.io.File,java.lang.String,java.lang.String,java.util.List,java.util.List) +com.threerings.getdown.data.EnvConfig$Note +com.threerings.getdown.data.EnvConfig$Note: com.threerings.getdown.data.EnvConfig$Note$Level level +com.threerings.getdown.data.EnvConfig$Note: java.lang.String message +com.threerings.getdown.data.EnvConfig$Note: com.threerings.getdown.data.EnvConfig$Note info(java.lang.String) +com.threerings.getdown.data.EnvConfig$Note: com.threerings.getdown.data.EnvConfig$Note warn(java.lang.String) +com.threerings.getdown.data.EnvConfig$Note: com.threerings.getdown.data.EnvConfig$Note error(java.lang.String) +com.threerings.getdown.data.EnvConfig$Note: EnvConfig$Note(com.threerings.getdown.data.EnvConfig$Note$Level,java.lang.String) +com.threerings.getdown.data.EnvConfig$Note$Level +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level INFO +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level WARN +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level ERROR +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level[] $VALUES +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level[] values() +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level valueOf(java.lang.String) +com.threerings.getdown.data.EnvConfig$Note$Level: EnvConfig$Note$Level(java.lang.String,int) +com.threerings.getdown.data.EnvConfig$Note$Level: void () +com.threerings.getdown.data.PathBuilder +com.threerings.getdown.data.PathBuilder: java.lang.String CODE_CACHE_DIR +com.threerings.getdown.data.PathBuilder: java.lang.String NATIVE_CACHE_DIR +com.threerings.getdown.data.PathBuilder: PathBuilder() +com.threerings.getdown.data.PathBuilder: com.threerings.getdown.data.ClassPath buildClassPath(com.threerings.getdown.data.Application) +com.threerings.getdown.data.PathBuilder: com.threerings.getdown.data.ClassPath buildDefaultClassPath(com.threerings.getdown.data.Application) +com.threerings.getdown.data.PathBuilder: com.threerings.getdown.data.ClassPath buildCachedClassPath(com.threerings.getdown.data.Application) +com.threerings.getdown.data.PathBuilder: com.threerings.getdown.data.ClassPath buildLibsPath(com.threerings.getdown.data.Application,boolean) +com.threerings.getdown.data.Properties +com.threerings.getdown.data.Properties: java.lang.String GETDOWN +com.threerings.getdown.data.Properties: java.lang.String CONNECT_PORT +com.threerings.getdown.data.Properties: Properties() +com.threerings.getdown.data.Resource +com.threerings.getdown.data.Resource: java.util.EnumSet NORMAL +com.threerings.getdown.data.Resource: java.util.EnumSet UNPACK +com.threerings.getdown.data.Resource: java.util.EnumSet EXEC +com.threerings.getdown.data.Resource: java.util.EnumSet PRELOAD +com.threerings.getdown.data.Resource: java.util.EnumSet NATIVE +com.threerings.getdown.data.Resource: java.lang.String _path +com.threerings.getdown.data.Resource: java.net.URL _remote +com.threerings.getdown.data.Resource: java.io.File _local +com.threerings.getdown.data.Resource: java.io.File _localNew +com.threerings.getdown.data.Resource: java.io.File _marker +com.threerings.getdown.data.Resource: java.io.File _unpacked +com.threerings.getdown.data.Resource: java.util.EnumSet _attrs +com.threerings.getdown.data.Resource: boolean _isJar +com.threerings.getdown.data.Resource: boolean _isPacked200Jar +com.threerings.getdown.data.Resource: java.util.Comparator ENTRY_COMP +com.threerings.getdown.data.Resource: int DIGEST_BUFFER_SIZE +com.threerings.getdown.data.Resource: java.lang.String computeDigest(int,java.io.File,java.security.MessageDigest,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.data.Resource: Resource(java.lang.String,java.net.URL,java.io.File,java.util.EnumSet) +com.threerings.getdown.data.Resource: java.lang.String getPath() +com.threerings.getdown.data.Resource: java.io.File getLocal() +com.threerings.getdown.data.Resource: java.io.File getLocalNew() +com.threerings.getdown.data.Resource: java.io.File getUnpacked() +com.threerings.getdown.data.Resource: java.io.File getFinalTarget() +com.threerings.getdown.data.Resource: java.net.URL getRemote() +com.threerings.getdown.data.Resource: boolean shouldUnpack() +com.threerings.getdown.data.Resource: boolean shouldPredownload() +com.threerings.getdown.data.Resource: boolean isNative() +com.threerings.getdown.data.Resource: java.lang.String computeDigest(int,java.security.MessageDigest,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.data.Resource: boolean isMarkedValid() +com.threerings.getdown.data.Resource: void markAsValid() +com.threerings.getdown.data.Resource: void clearMarker() +com.threerings.getdown.data.Resource: void install(boolean) +com.threerings.getdown.data.Resource: void unpack() +com.threerings.getdown.data.Resource: void applyAttrs() +com.threerings.getdown.data.Resource: void erase() +com.threerings.getdown.data.Resource: int compareTo(com.threerings.getdown.data.Resource) +com.threerings.getdown.data.Resource: boolean equals(java.lang.Object) +com.threerings.getdown.data.Resource: int hashCode() +com.threerings.getdown.data.Resource: java.lang.String toString() +com.threerings.getdown.data.Resource: void updateProgress(com.threerings.getdown.util.ProgressObserver,long,long) +com.threerings.getdown.data.Resource: boolean isJar(java.lang.String) +com.threerings.getdown.data.Resource: boolean isPacked200Jar(java.lang.String) +com.threerings.getdown.data.Resource: int compareTo(java.lang.Object) +com.threerings.getdown.data.Resource: void () +com.threerings.getdown.data.Resource$Attr +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr UNPACK +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr CLEAN +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr EXEC +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr PRELOAD +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr NATIVE +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr[] $VALUES +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr[] values() +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr valueOf(java.lang.String) +com.threerings.getdown.data.Resource$Attr: Resource$Attr(java.lang.String,int) +com.threerings.getdown.data.Resource$Attr: void () +com.threerings.getdown.data.SysProps +com.threerings.getdown.data.SysProps: SysProps() +com.threerings.getdown.data.SysProps: java.lang.String appDir() +com.threerings.getdown.data.SysProps: java.lang.String appId() +com.threerings.getdown.data.SysProps: java.lang.String appBase() +com.threerings.getdown.data.SysProps: boolean noLogRedir() +com.threerings.getdown.data.SysProps: java.lang.String appbaseDomain() +com.threerings.getdown.data.SysProps: java.lang.String appbaseOverride() +com.threerings.getdown.data.SysProps: boolean silent() +com.threerings.getdown.data.SysProps: boolean launchInSilent() +com.threerings.getdown.data.SysProps: boolean noUpdate() +com.threerings.getdown.data.SysProps: boolean noInstall() +com.threerings.getdown.data.SysProps: int startDelay() +com.threerings.getdown.data.SysProps: boolean noUnpack() +com.threerings.getdown.data.SysProps: boolean direct() +com.threerings.getdown.data.SysProps: int connectTimeout() +com.threerings.getdown.data.SysProps: int readTimeout() +com.threerings.getdown.data.SysProps: int threadPoolSize() +com.threerings.getdown.data.SysProps: long parseJavaVersion(java.lang.String,java.lang.String) +com.threerings.getdown.data.SysProps: java.lang.String overrideAppbase(java.lang.String) +com.threerings.getdown.data.SysProps: java.lang.String replaceDomain(java.lang.String) +com.threerings.getdown.launcher.AbortPanel +com.threerings.getdown.launcher.AbortPanel: com.threerings.getdown.launcher.Getdown _getdown +com.threerings.getdown.launcher.AbortPanel: java.util.ResourceBundle _msgs +com.threerings.getdown.launcher.AbortPanel: AbortPanel(com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) +com.threerings.getdown.launcher.AbortPanel: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.AbortPanel: void actionPerformed(java.awt.event.ActionEvent) +com.threerings.getdown.launcher.AbortPanel: java.lang.String get(java.lang.String) +com.threerings.getdown.launcher.Getdown +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.util.ProgressObserver _progobs +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.data.Application _app +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.data.Application$UpdateInterface _ifc +com.threerings.getdown.launcher.Getdown: java.util.ResourceBundle _msgs +com.threerings.getdown.launcher.Getdown: java.awt.Container _container +com.threerings.getdown.launcher.Getdown: javax.swing.JLayeredPane _layers +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.launcher.StatusPanel _status +com.threerings.getdown.launcher.Getdown: javax.swing.JButton _patchNotes +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.launcher.AbortPanel _abort +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.launcher.RotatingBackgrounds _background +com.threerings.getdown.launcher.Getdown: boolean _dead +com.threerings.getdown.launcher.Getdown: boolean _silent +com.threerings.getdown.launcher.Getdown: boolean _launchInSilent +com.threerings.getdown.launcher.Getdown: boolean _noUpdate +com.threerings.getdown.launcher.Getdown: long _startup +com.threerings.getdown.launcher.Getdown: java.util.Set _toInstallResources +com.threerings.getdown.launcher.Getdown: boolean _readyToInstall +com.threerings.getdown.launcher.Getdown: boolean _enableTracking +com.threerings.getdown.launcher.Getdown: int _reportedProgress +com.threerings.getdown.launcher.Getdown: int _delay +com.threerings.getdown.launcher.Getdown: int _stepMaxPercent +com.threerings.getdown.launcher.Getdown: int _stepMinPercent +com.threerings.getdown.launcher.Getdown: int _lastGlobalPercent +com.threerings.getdown.launcher.Getdown: int _uiDisplayPercent +com.threerings.getdown.launcher.Getdown: int MAX_LOOPS +com.threerings.getdown.launcher.Getdown: long FALLBACK_CHECK_TIME +com.threerings.getdown.launcher.Getdown: Getdown(com.threerings.getdown.data.EnvConfig) +com.threerings.getdown.launcher.Getdown: boolean isUpdateAvailable() +com.threerings.getdown.launcher.Getdown: void install() +com.threerings.getdown.launcher.Getdown: void run() +com.threerings.getdown.launcher.Getdown: void configProxy(java.lang.String,java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.launcher.Getdown: boolean detectProxy() +com.threerings.getdown.launcher.Getdown: void readConfig(boolean) +com.threerings.getdown.launcher.Getdown: void doPredownloads(java.util.Collection) +com.threerings.getdown.launcher.Getdown: void getdown() +com.threerings.getdown.launcher.Getdown: void updateStatus(java.lang.String) +com.threerings.getdown.launcher.Getdown: java.awt.image.BufferedImage loadImage(java.lang.String) +com.threerings.getdown.launcher.Getdown: void updateJava() +com.threerings.getdown.launcher.Getdown: void update() +com.threerings.getdown.launcher.Getdown: void download(java.util.Collection) +com.threerings.getdown.launcher.Getdown: void launch() +com.threerings.getdown.launcher.Getdown: void createInterfaceAsync(boolean) +com.threerings.getdown.launcher.Getdown: void initInterface() +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.launcher.RotatingBackgrounds getBackground() +com.threerings.getdown.launcher.Getdown: java.awt.Image getProgressImage() +com.threerings.getdown.launcher.Getdown: void handleWindowClose() +com.threerings.getdown.launcher.Getdown: void fail(java.lang.String) +com.threerings.getdown.launcher.Getdown: void setStep(com.threerings.getdown.data.Application$UpdateInterface$Step) +com.threerings.getdown.launcher.Getdown: int stepToGlobalPercent(int) +com.threerings.getdown.launcher.Getdown: void setStatusAsync(java.lang.String,int,long,boolean) +com.threerings.getdown.launcher.Getdown: void reportTrackingEvent(java.lang.String,int) +com.threerings.getdown.launcher.Getdown: java.awt.Container createContainer() +com.threerings.getdown.launcher.Getdown: void configureContainer() +com.threerings.getdown.launcher.Getdown: void showContainer() +com.threerings.getdown.launcher.Getdown: void disposeContainer() +com.threerings.getdown.launcher.Getdown: boolean invokeDirect() +com.threerings.getdown.launcher.Getdown: void showDocument(java.lang.String) +com.threerings.getdown.launcher.Getdown: void exit(int) +com.threerings.getdown.launcher.Getdown: void copyStream(java.io.InputStream,java.io.PrintStream) +com.threerings.getdown.launcher.Getdown: java.awt.Image loadImage(java.lang.String) +com.threerings.getdown.launcher.Getdown$ProgressReporter +com.threerings.getdown.launcher.Getdown$ProgressReporter: java.net.URL _url +com.threerings.getdown.launcher.Getdown$ProgressReporter: com.threerings.getdown.launcher.Getdown this$0 +com.threerings.getdown.launcher.Getdown$ProgressReporter: Getdown$ProgressReporter(com.threerings.getdown.launcher.Getdown,java.net.URL) +com.threerings.getdown.launcher.Getdown$ProgressReporter: void run() +com.threerings.getdown.launcher.GetdownApp +com.threerings.getdown.launcher.GetdownApp: GetdownApp() +com.threerings.getdown.launcher.GetdownApp: void main(java.lang.String[]) +com.threerings.getdown.launcher.GetdownApp: com.threerings.getdown.launcher.Getdown start(java.lang.String[]) +com.threerings.getdown.launcher.MultipleGetdownRunning +com.threerings.getdown.launcher.MultipleGetdownRunning: MultipleGetdownRunning() +com.threerings.getdown.launcher.ProxyPanel +com.threerings.getdown.launcher.ProxyPanel: com.threerings.getdown.launcher.Getdown _getdown +com.threerings.getdown.launcher.ProxyPanel: java.util.ResourceBundle _msgs +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JTextField _host +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JTextField _port +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JCheckBox _useAuth +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JTextField _username +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JPasswordField _password +com.threerings.getdown.launcher.ProxyPanel: ProxyPanel(com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) +com.threerings.getdown.launcher.ProxyPanel: void setProxy(java.lang.String,java.lang.String) +com.threerings.getdown.launcher.ProxyPanel: void addNotify() +com.threerings.getdown.launcher.ProxyPanel: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.ProxyPanel: void actionPerformed(java.awt.event.ActionEvent) +com.threerings.getdown.launcher.ProxyPanel: java.lang.String get(java.lang.String) +com.threerings.getdown.launcher.ProxyPanel: java.awt.Dimension clampWidth(java.awt.Dimension,int) +com.threerings.getdown.launcher.ProxyPanel$SaneLabelField +com.threerings.getdown.launcher.ProxyPanel$SaneLabelField: ProxyPanel$SaneLabelField(java.lang.String) +com.threerings.getdown.launcher.ProxyPanel$SaneLabelField: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.ProxyPanel$SanePasswordField +com.threerings.getdown.launcher.ProxyPanel$SanePasswordField: ProxyPanel$SanePasswordField() +com.threerings.getdown.launcher.ProxyPanel$SanePasswordField: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.ProxyPanel$SaneTextField +com.threerings.getdown.launcher.ProxyPanel$SaneTextField: ProxyPanel$SaneTextField() +com.threerings.getdown.launcher.ProxyPanel$SaneTextField: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.ProxyUtil +com.threerings.getdown.launcher.ProxyUtil: java.lang.String PROXY_REGISTRY +com.threerings.getdown.launcher.ProxyUtil: ProxyUtil() +com.threerings.getdown.launcher.ProxyUtil: boolean autoDetectProxy(com.threerings.getdown.data.Application) +com.threerings.getdown.launcher.ProxyUtil: boolean canLoadWithoutProxy(java.net.URL) +com.threerings.getdown.launcher.ProxyUtil: void configProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.launcher.ProxyUtil: java.lang.String[] loadProxy(com.threerings.getdown.data.Application) +com.threerings.getdown.launcher.ProxyUtil: void saveProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String) +com.threerings.getdown.launcher.ProxyUtil: void initProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.launcher.RotatingBackgrounds +com.threerings.getdown.launcher.RotatingBackgrounds: long currentDisplayStart +com.threerings.getdown.launcher.RotatingBackgrounds: int current +com.threerings.getdown.launcher.RotatingBackgrounds: java.awt.Image[] images +com.threerings.getdown.launcher.RotatingBackgrounds: java.awt.Image errorImage +com.threerings.getdown.launcher.RotatingBackgrounds: int[] percentages +com.threerings.getdown.launcher.RotatingBackgrounds: int[] minDisplayTime +com.threerings.getdown.launcher.RotatingBackgrounds: RotatingBackgrounds() +com.threerings.getdown.launcher.RotatingBackgrounds: RotatingBackgrounds(java.awt.Image) +com.threerings.getdown.launcher.RotatingBackgrounds: RotatingBackgrounds(java.util.List,java.lang.String,com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader) +com.threerings.getdown.launcher.RotatingBackgrounds: java.awt.Image getImage(int) +com.threerings.getdown.launcher.RotatingBackgrounds: java.awt.Image getErrorImage() +com.threerings.getdown.launcher.RotatingBackgrounds: int getNumImages() +com.threerings.getdown.launcher.RotatingBackgrounds: void makeEmpty() +com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader +com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader: java.awt.Image loadImage(java.lang.String) +com.threerings.getdown.launcher.StatusPanel +com.threerings.getdown.launcher.StatusPanel: java.awt.Image _barimg +com.threerings.getdown.launcher.StatusPanel: com.threerings.getdown.launcher.RotatingBackgrounds _bg +com.threerings.getdown.launcher.StatusPanel: java.awt.Dimension _psize +com.threerings.getdown.launcher.StatusPanel: java.util.ResourceBundle _msgs +com.threerings.getdown.launcher.StatusPanel: int _progress +com.threerings.getdown.launcher.StatusPanel: java.lang.String _status +com.threerings.getdown.launcher.StatusPanel: int _statusDots +com.threerings.getdown.launcher.StatusPanel: boolean _displayError +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _label +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _newlab +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _plabel +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _newplab +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _rlabel +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _newrlab +com.threerings.getdown.launcher.StatusPanel: com.threerings.getdown.data.Application$UpdateInterface _ifc +com.threerings.getdown.launcher.StatusPanel: javax.swing.Timer _timer +com.threerings.getdown.launcher.StatusPanel: long[] _remain +com.threerings.getdown.launcher.StatusPanel: int _ridx +com.threerings.getdown.launcher.StatusPanel: com.samskivert.util.Throttle _rthrottle +com.threerings.getdown.launcher.StatusPanel: java.awt.Font FONT +com.threerings.getdown.launcher.StatusPanel: StatusPanel(java.util.ResourceBundle) +com.threerings.getdown.launcher.StatusPanel: void init(com.threerings.getdown.data.Application$UpdateInterface,com.threerings.getdown.launcher.RotatingBackgrounds,java.awt.Image) +com.threerings.getdown.launcher.StatusPanel: boolean imageUpdate(java.awt.Image,int,int,int,int,int) +com.threerings.getdown.launcher.StatusPanel: void setProgress(int,long) +com.threerings.getdown.launcher.StatusPanel: void setStatus(java.lang.String,boolean) +com.threerings.getdown.launcher.StatusPanel: void stopThrob() +com.threerings.getdown.launcher.StatusPanel: void addNotify() +com.threerings.getdown.launcher.StatusPanel: void removeNotify() +com.threerings.getdown.launcher.StatusPanel: void paintComponent(java.awt.Graphics) +com.threerings.getdown.launcher.StatusPanel: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.StatusPanel: void updateStatusLabel() +com.threerings.getdown.launcher.StatusPanel: int getStatusY(com.samskivert.swing.Label) +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label createLabel(java.lang.String,java.awt.Color) +com.threerings.getdown.launcher.StatusPanel: java.lang.String xlate(java.lang.String) +com.threerings.getdown.launcher.StatusPanel: java.lang.String get(java.lang.String,java.lang.String[]) +com.threerings.getdown.launcher.StatusPanel: java.lang.String get(java.lang.String) +com.threerings.getdown.launcher.StatusPanel: void () +com.threerings.getdown.net.Downloader +com.threerings.getdown.net.Downloader: java.util.Map _sizes +com.threerings.getdown.net.Downloader: java.util.Map _downloaded +com.threerings.getdown.net.Downloader: long _start +com.threerings.getdown.net.Downloader: long _bytesPerSecond +com.threerings.getdown.net.Downloader: long _lastUpdate +com.threerings.getdown.net.Downloader: com.threerings.getdown.net.Downloader$State _state +com.threerings.getdown.net.Downloader: long UPDATE_DELAY +com.threerings.getdown.net.Downloader: Downloader() +com.threerings.getdown.net.Downloader: boolean download(java.util.Collection,int) +com.threerings.getdown.net.Downloader: void abort() +com.threerings.getdown.net.Downloader: void resolvingDownloads() +com.threerings.getdown.net.Downloader: void downloadProgress(int,long) +com.threerings.getdown.net.Downloader: void downloadFailed(com.threerings.getdown.data.Resource,java.lang.Exception) +com.threerings.getdown.net.Downloader: long checkSize(com.threerings.getdown.data.Resource) +com.threerings.getdown.net.Downloader: void reportProgress(com.threerings.getdown.data.Resource,long,long) +com.threerings.getdown.net.Downloader: long sum(java.lang.Iterable) +com.threerings.getdown.net.Downloader: void download(com.threerings.getdown.data.Resource) +com.threerings.getdown.net.Downloader$State +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State DOWNLOADING +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State COMPLETE +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State FAILED +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State ABORTED +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State[] $VALUES +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State[] values() +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State valueOf(java.lang.String) +com.threerings.getdown.net.Downloader$State: Downloader$State(java.lang.String,int) +com.threerings.getdown.net.Downloader$State: void () +com.threerings.getdown.net.HTTPDownloader +com.threerings.getdown.net.HTTPDownloader: java.net.Proxy _proxy +com.threerings.getdown.net.HTTPDownloader: HTTPDownloader(java.net.Proxy) +com.threerings.getdown.net.HTTPDownloader: long checkSize(com.threerings.getdown.data.Resource) +com.threerings.getdown.net.HTTPDownloader: void download(com.threerings.getdown.data.Resource) +com.threerings.getdown.spi.ProxyAuth +com.threerings.getdown.spi.ProxyAuth: com.threerings.getdown.spi.ProxyAuth$Credentials loadCredentials(java.lang.String) +com.threerings.getdown.spi.ProxyAuth: void saveCredentials(java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.spi.ProxyAuth$Credentials +com.threerings.getdown.spi.ProxyAuth$Credentials: java.lang.String username +com.threerings.getdown.spi.ProxyAuth$Credentials: java.lang.String password +com.threerings.getdown.spi.ProxyAuth$Credentials: ProxyAuth$Credentials(java.lang.String,java.lang.String) +com.threerings.getdown.tools.Differ +com.threerings.getdown.tools.Differ: Differ() +com.threerings.getdown.tools.Differ: void createDiff(java.io.File,java.io.File,boolean) +com.threerings.getdown.tools.Differ: void createPatch(java.io.File,java.util.ArrayList,java.util.ArrayList,boolean) +com.threerings.getdown.tools.Differ: java.io.File rebuildJar(java.io.File) +com.threerings.getdown.tools.Differ: void jarDiff(java.io.File,java.io.File,java.util.jar.JarOutputStream) +com.threerings.getdown.tools.Differ: void main(java.lang.String[]) +com.threerings.getdown.tools.Differ: void pipe(java.io.File,java.util.jar.JarOutputStream) +com.threerings.getdown.tools.Digester +com.threerings.getdown.tools.Digester: Digester() +com.threerings.getdown.tools.Digester: void main(java.lang.String[]) +com.threerings.getdown.tools.Digester: void createDigests(java.io.File,java.io.File,java.lang.String,java.lang.String) +com.threerings.getdown.tools.Digester: void createDigest(int,java.io.File) +com.threerings.getdown.tools.Digester: void signDigest(int,java.io.File,java.io.File,java.lang.String,java.lang.String) +com.threerings.getdown.tools.JarDiff +com.threerings.getdown.tools.JarDiff: int DEFAULT_READ_SIZE +com.threerings.getdown.tools.JarDiff: byte[] newBytes +com.threerings.getdown.tools.JarDiff: byte[] oldBytes +com.threerings.getdown.tools.JarDiff: boolean _debug +com.threerings.getdown.tools.JarDiff: JarDiff() +com.threerings.getdown.tools.JarDiff: void createPatch(java.lang.String,java.lang.String,java.io.OutputStream,boolean) +com.threerings.getdown.tools.JarDiff: void createIndex(java.util.jar.JarOutputStream,java.util.List,java.util.Map) +com.threerings.getdown.tools.JarDiff: java.io.Writer writeEscapedString(java.io.Writer,java.lang.String) +com.threerings.getdown.tools.JarDiff: void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,com.threerings.getdown.tools.JarDiff$JarFile2) +com.threerings.getdown.tools.JarDiff: byte[] access$000() +com.threerings.getdown.tools.JarDiff: byte[] access$100() +com.threerings.getdown.tools.JarDiff: boolean access$200() +com.threerings.getdown.tools.JarDiff: void () +com.threerings.getdown.tools.JarDiffCodes +com.threerings.getdown.tools.JarDiffCodes: java.lang.String INDEX_NAME +com.threerings.getdown.tools.JarDiffCodes: java.lang.String VERSION_HEADER +com.threerings.getdown.tools.JarDiffCodes: java.lang.String REMOVE_COMMAND +com.threerings.getdown.tools.JarDiffCodes: java.lang.String MOVE_COMMAND +com.threerings.getdown.tools.JarDiffPatcher +com.threerings.getdown.tools.JarDiffPatcher: int DEFAULT_READ_SIZE +com.threerings.getdown.tools.JarDiffPatcher: byte[] newBytes +com.threerings.getdown.tools.JarDiffPatcher: byte[] oldBytes +com.threerings.getdown.tools.JarDiffPatcher: JarDiffPatcher() +com.threerings.getdown.tools.JarDiffPatcher: void patchJar(java.lang.String,java.lang.String,java.io.File,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.tools.JarDiffPatcher: void updateObserver(com.threerings.getdown.util.ProgressObserver,double,double) +com.threerings.getdown.tools.JarDiffPatcher: void determineNameMapping(java.util.jar.JarFile,java.util.Set,java.util.Map) +com.threerings.getdown.tools.JarDiffPatcher: java.util.List getSubpaths(java.lang.String) +com.threerings.getdown.tools.JarDiffPatcher: void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,java.util.jar.JarFile) +com.threerings.getdown.tools.JarDiffPatcher: void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,java.io.InputStream) +com.threerings.getdown.tools.JarDiffPatcher: void () +com.threerings.getdown.tools.Patcher +com.threerings.getdown.tools.Patcher: java.lang.String CREATE +com.threerings.getdown.tools.Patcher: java.lang.String PATCH +com.threerings.getdown.tools.Patcher: java.lang.String DELETE +com.threerings.getdown.tools.Patcher: com.threerings.getdown.util.ProgressObserver _obs +com.threerings.getdown.tools.Patcher: long _complete +com.threerings.getdown.tools.Patcher: long _plength +com.threerings.getdown.tools.Patcher: byte[] _buffer +com.threerings.getdown.tools.Patcher: int COPY_BUFFER_SIZE +com.threerings.getdown.tools.Patcher: Patcher() +com.threerings.getdown.tools.Patcher: void patch(java.io.File,java.io.File,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.tools.Patcher: java.lang.String strip(java.lang.String,java.lang.String) +com.threerings.getdown.tools.Patcher: void createFile(java.util.jar.JarFile,java.util.zip.ZipEntry,java.io.File) +com.threerings.getdown.tools.Patcher: void patchFile(java.util.jar.JarFile,java.util.zip.ZipEntry,java.io.File,java.lang.String) +com.threerings.getdown.tools.Patcher: void updateProgress(int) +com.threerings.getdown.tools.Patcher: void main(java.lang.String[]) +com.threerings.getdown.util.Base64 +com.threerings.getdown.util.Base64: int DEFAULT +com.threerings.getdown.util.Base64: int NO_PADDING +com.threerings.getdown.util.Base64: int NO_WRAP +com.threerings.getdown.util.Base64: int CRLF +com.threerings.getdown.util.Base64: int URL_SAFE +com.threerings.getdown.util.Base64: int NO_CLOSE +com.threerings.getdown.util.Base64: boolean $assertionsDisabled +com.threerings.getdown.util.Base64: byte[] decode(java.lang.String,int) +com.threerings.getdown.util.Base64: byte[] decode(byte[],int) +com.threerings.getdown.util.Base64: byte[] decode(byte[],int,int,int) +com.threerings.getdown.util.Base64: java.lang.String encodeToString(byte[],int) +com.threerings.getdown.util.Base64: java.lang.String encodeToString(byte[],int,int,int) +com.threerings.getdown.util.Base64: byte[] encode(byte[],int) +com.threerings.getdown.util.Base64: byte[] encode(byte[],int,int,int) +com.threerings.getdown.util.Base64: Base64() +com.threerings.getdown.util.Base64: void () +com.threerings.getdown.util.Color +com.threerings.getdown.util.Color: int CLEAR +com.threerings.getdown.util.Color: int WHITE +com.threerings.getdown.util.Color: int BLACK +com.threerings.getdown.util.Color: float brightness(int) +com.threerings.getdown.util.Color: Color() +com.threerings.getdown.util.Config +com.threerings.getdown.util.Config: com.threerings.getdown.util.Config EMPTY +com.threerings.getdown.util.Config: java.util.Map _data +com.threerings.getdown.util.Config: com.threerings.getdown.util.Config$ParseOpts createOpts(boolean) +com.threerings.getdown.util.Config: java.util.List parsePairs(java.io.File,com.threerings.getdown.util.Config$ParseOpts) +com.threerings.getdown.util.Config: java.util.List parsePairs(java.io.Reader,com.threerings.getdown.util.Config$ParseOpts) +com.threerings.getdown.util.Config: com.threerings.getdown.util.Rectangle parseRect(java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: java.lang.Integer parseColor(java.lang.String) +com.threerings.getdown.util.Config: com.threerings.getdown.util.Config parseConfig(java.io.File,com.threerings.getdown.util.Config$ParseOpts) +com.threerings.getdown.util.Config: Config(java.util.Map) +com.threerings.getdown.util.Config: boolean hasValue(java.lang.String) +com.threerings.getdown.util.Config: java.lang.Object getRaw(java.lang.String) +com.threerings.getdown.util.Config: java.lang.String getString(java.lang.String) +com.threerings.getdown.util.Config: java.lang.String getString(java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: boolean getBoolean(java.lang.String) +com.threerings.getdown.util.Config: java.lang.String[] getMultiValue(java.lang.String) +com.threerings.getdown.util.Config: com.threerings.getdown.util.Rectangle getRect(java.lang.String,com.threerings.getdown.util.Rectangle) +com.threerings.getdown.util.Config: int getInt(java.lang.String,int) +com.threerings.getdown.util.Config: long getLong(java.lang.String,long) +com.threerings.getdown.util.Config: int getColor(java.lang.String,int) +com.threerings.getdown.util.Config: java.lang.String[] getList(java.lang.String) +com.threerings.getdown.util.Config: java.lang.String getUrl(java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: boolean checkQualifiers(java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: boolean checkQualifier(java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: void () +com.threerings.getdown.util.Config$ParseOpts +com.threerings.getdown.util.Config$ParseOpts: boolean biasToKey +com.threerings.getdown.util.Config$ParseOpts: boolean strictComments +com.threerings.getdown.util.Config$ParseOpts: java.lang.String osname +com.threerings.getdown.util.Config$ParseOpts: java.lang.String osarch +com.threerings.getdown.util.Config$ParseOpts: Config$ParseOpts() +com.threerings.getdown.util.ConnectionUtil +com.threerings.getdown.util.ConnectionUtil: ConnectionUtil() +com.threerings.getdown.util.ConnectionUtil: java.net.URLConnection open(java.net.Proxy,java.net.URL,int,int) +com.threerings.getdown.util.ConnectionUtil: java.net.HttpURLConnection openHttp(java.net.Proxy,java.net.URL,int,int) +com.threerings.getdown.util.FileUtil +com.threerings.getdown.util.FileUtil: FileUtil() +com.threerings.getdown.util.FileUtil: boolean renameTo(java.io.File,java.io.File) +com.threerings.getdown.util.FileUtil: boolean deleteHarder(java.io.File) +com.threerings.getdown.util.FileUtil: boolean deleteDirHarder(java.io.File) +com.threerings.getdown.util.FileUtil: java.util.List readLines(java.io.Reader) +com.threerings.getdown.util.FileUtil: void unpackJar(java.util.jar.JarFile,java.io.File,boolean) +com.threerings.getdown.util.FileUtil: void unpackPacked200Jar(java.io.File,java.io.File) +com.threerings.getdown.util.FileUtil: void copy(java.io.File,java.io.File) +com.threerings.getdown.util.FileUtil: void makeExecutable(java.io.File) +com.threerings.getdown.util.FileUtil: void walkTree(java.io.File,com.threerings.getdown.util.FileUtil$Visitor) +com.threerings.getdown.util.FileUtil$Visitor +com.threerings.getdown.util.FileUtil$Visitor: void visit(java.io.File) +com.threerings.getdown.util.HostWhitelist +com.threerings.getdown.util.HostWhitelist: HostWhitelist() +com.threerings.getdown.util.HostWhitelist: java.net.URL verify(java.net.URL) +com.threerings.getdown.util.HostWhitelist: java.net.URL verify(java.util.List,java.net.URL) +com.threerings.getdown.util.LaunchUtil +com.threerings.getdown.util.LaunchUtil: java.lang.String LOCAL_JAVA_DIR +com.threerings.getdown.util.LaunchUtil: boolean _isWindows +com.threerings.getdown.util.LaunchUtil: boolean _isMacOS +com.threerings.getdown.util.LaunchUtil: boolean _isLinux +com.threerings.getdown.util.LaunchUtil: LaunchUtil() +com.threerings.getdown.util.LaunchUtil: boolean updateVersionAndRelaunch(java.io.File,java.lang.String,java.lang.String) +com.threerings.getdown.util.LaunchUtil: java.lang.String getJVMPath(java.io.File) +com.threerings.getdown.util.LaunchUtil: java.lang.String getJVMPath(java.io.File,boolean) +com.threerings.getdown.util.LaunchUtil: void upgradeGetdown(java.io.File,java.io.File,java.io.File) +com.threerings.getdown.util.LaunchUtil: boolean mustMonitorChildren() +com.threerings.getdown.util.LaunchUtil: boolean isWindows() +com.threerings.getdown.util.LaunchUtil: boolean isMacOS() +com.threerings.getdown.util.LaunchUtil: boolean isLinux() +com.threerings.getdown.util.LaunchUtil: java.lang.String checkJVMPath(java.lang.String,boolean) +com.threerings.getdown.util.LaunchUtil: void () +com.threerings.getdown.util.MessageUtil +com.threerings.getdown.util.MessageUtil: java.lang.String TAINT_CHAR +com.threerings.getdown.util.MessageUtil: MessageUtil() +com.threerings.getdown.util.MessageUtil: boolean isTainted(java.lang.String) +com.threerings.getdown.util.MessageUtil: java.lang.String taint(java.lang.Object) +com.threerings.getdown.util.MessageUtil: java.lang.String untaint(java.lang.String) +com.threerings.getdown.util.MessageUtil: java.lang.String compose(java.lang.String,java.lang.Object[]) +com.threerings.getdown.util.MessageUtil: java.lang.String compose(java.lang.String,java.lang.String[]) +com.threerings.getdown.util.MessageUtil: java.lang.String tcompose(java.lang.String,java.lang.Object[]) +com.threerings.getdown.util.MessageUtil: java.lang.String tcompose(java.lang.String,java.lang.String[]) +com.threerings.getdown.util.MessageUtil: java.lang.String escape(java.lang.String) +com.threerings.getdown.util.MessageUtil: java.lang.String unescape(java.lang.String) +com.threerings.getdown.util.ProgressAggregator +com.threerings.getdown.util.ProgressAggregator: com.threerings.getdown.util.ProgressObserver _target +com.threerings.getdown.util.ProgressAggregator: long[] _sizes +com.threerings.getdown.util.ProgressAggregator: int[] _progress +com.threerings.getdown.util.ProgressAggregator: ProgressAggregator(com.threerings.getdown.util.ProgressObserver,long[]) +com.threerings.getdown.util.ProgressAggregator: com.threerings.getdown.util.ProgressObserver startElement(int) +com.threerings.getdown.util.ProgressAggregator: void updateAggProgress() +com.threerings.getdown.util.ProgressAggregator: long sum(long[]) +com.threerings.getdown.util.ProgressObserver +com.threerings.getdown.util.ProgressObserver: void progress(int) +com.threerings.getdown.util.Rectangle +com.threerings.getdown.util.Rectangle: int x +com.threerings.getdown.util.Rectangle: int y +com.threerings.getdown.util.Rectangle: int width +com.threerings.getdown.util.Rectangle: int height +com.threerings.getdown.util.Rectangle: Rectangle(int,int,int,int) +com.threerings.getdown.util.Rectangle: com.threerings.getdown.util.Rectangle union(com.threerings.getdown.util.Rectangle) +com.threerings.getdown.util.Rectangle: java.lang.String toString() +com.threerings.getdown.util.StreamUtil +com.threerings.getdown.util.StreamUtil: StreamUtil() +com.threerings.getdown.util.StreamUtil: void close(java.io.InputStream) +com.threerings.getdown.util.StreamUtil: void close(java.io.OutputStream) +com.threerings.getdown.util.StreamUtil: void close(java.io.Reader) +com.threerings.getdown.util.StreamUtil: void close(java.io.Writer) +com.threerings.getdown.util.StreamUtil: java.io.OutputStream copy(java.io.InputStream,java.io.OutputStream) +com.threerings.getdown.util.StreamUtil: byte[] toByteArray(java.io.InputStream) +com.threerings.getdown.util.StringUtil +com.threerings.getdown.util.StringUtil: java.lang.String XLATE +com.threerings.getdown.util.StringUtil: StringUtil() +com.threerings.getdown.util.StringUtil: boolean couldBeValidUrl(java.lang.String) +com.threerings.getdown.util.StringUtil: boolean isBlank(java.lang.String) +com.threerings.getdown.util.StringUtil: int[] parseIntArray(java.lang.String) +com.threerings.getdown.util.StringUtil: java.lang.String[] parseStringArray(java.lang.String) +com.threerings.getdown.util.StringUtil: java.lang.String[] parseStringArray(java.lang.String,boolean) +com.threerings.getdown.util.StringUtil: java.lang.String deNull(java.lang.String) +com.threerings.getdown.util.StringUtil: java.lang.String hexlate(byte[],int) +com.threerings.getdown.util.StringUtil: java.lang.String hexlate(byte[]) +com.threerings.getdown.util.StringUtil: java.lang.String join(java.lang.Object[]) +com.threerings.getdown.util.StringUtil: java.lang.String join(java.lang.Object[],boolean) +com.threerings.getdown.util.StringUtil: java.lang.String join(java.lang.Object[],java.lang.String) +com.threerings.getdown.util.StringUtil: java.lang.String join(java.lang.Object[],java.lang.String,boolean) +com.threerings.getdown.util.VersionUtil +com.threerings.getdown.util.VersionUtil: VersionUtil() +com.threerings.getdown.util.VersionUtil: long readVersion(java.io.File) +com.threerings.getdown.util.VersionUtil: void writeVersion(java.io.File,long) +com.threerings.getdown.util.VersionUtil: long parseJavaVersion(java.lang.String,java.lang.String) +com.threerings.getdown.util.VersionUtil: long readReleaseVersion(java.io.File,java.lang.String) +com.threerings.getdown.util.VersionUtil: int parseInt(java.lang.String) diff --git a/getdown/src/getdown/lib/SOURCE_HEADER b/getdown/src/getdown/lib/SOURCE_HEADER new file mode 100644 index 0000000..43271fe --- /dev/null +++ b/getdown/src/getdown/lib/SOURCE_HEADER @@ -0,0 +1,5 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + diff --git a/getdown/src/getdown/lib/jRegistryKey.dll b/getdown/src/getdown/lib/jRegistryKey.dll new file mode 100644 index 0000000000000000000000000000000000000000..57467285b43c35db50d11053a962376da0c93d4f GIT binary patch literal 29316 zcmeHw3w#^ZneW&!jtK?}2oSoV71W^y@~~|Al^Bo{3u15_S&HSvA(0hZj%*~^Sfh|o zxCCNRw1aOAg%*0DB!#e|i3{tomP`zbe=nwf6LOZ|wDlt!@5Dq+7Ob2v~b#5o>3}TD#I?4R^N(7M^TV9!YZ1bE0djB+VuIU-Gq34s4czbM^xKA%;lS zln{8w)r$WYw)S!zSsT{SL@ftKH|C;*uqJgHwhucQyBx0W{ zvHZlkW81#Sl2<0=!W_kENz@kRDbBn^PGRzjZ%ER%(R}14_R59vqp?$VNUl&gJ{Ysu z-box()*Vw~pC?Aw?a7ytS3$Iq5o8@qUQVyC4doPW#Y^(KOC;$bUQIq{2oNMKz>-V? zdvY88jKq`GC^RJHBhNNC@<`$+<1a6=tK4k`uEtoPcS~KQqZ|0F9sbTxpxt_XryR5fdV9Njt&v!GL!j3>FVwzJ z>fa>lp8$K#&251mxwAVW4KRH)dp);|=D-Onu{@(L*d|k z_~Y@84{S!E#2#q0uO4rWD2ehYJ#ZtE!E@o;T;62YOxW5!OJa!16(XZc>W|MJA0O|+ z8~@MMAGZMD;;H5#mLlngSV4MA&Op-xLwpno@bMt=u|v;CJRc}T&ZCB$JYCLHhMXK- z&U5;ly$Zt@ACzS!PWAEDrNpT`-df}D$k!;jNc1&?Oma{G4Q0a+^KMx_r_q73~ikS-al1Zcm=%>VIGk@`Jx6=x?`bsD~;wROg8tu8F_i

(O! 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 + + 4.0.0 + jregistrykey + jregistrykey + 1.0 + POM was created from install:install-file + diff --git a/getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml b/getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml new file mode 100644 index 0000000..1a8a725 --- /dev/null +++ b/getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + jregistrykey + jregistrykey + 1.0 + + + 1.0 + + 20101118155146 + + diff --git a/getdown/src/getdown/lib/manifest.mf b/getdown/src/getdown/lib/manifest.mf new file mode 100644 index 0000000..3be50cc --- /dev/null +++ b/getdown/src/getdown/lib/manifest.mf @@ -0,0 +1,6 @@ +Main-Class: com.threerings.getdown.launcher.Getdown +Permissions: all-permissions +Application-Name: Getdown +Codebase: * +Application-Library-Allowable-Codebase: * +Caller-Allowable-Codebase: * diff --git a/getdown/src/getdown/pom.xml b/getdown/src/getdown/pom.xml new file mode 100644 index 0000000..c6af03d --- /dev/null +++ b/getdown/src/getdown/pom.xml @@ -0,0 +1,180 @@ + + + 4.0.0 + + org.sonatype.oss + oss-parent + 7 + + + com.threerings.getdown + getdown + pom + 1.8.3-SNAPSHOT + + getdown + An application installer and updater. + https://github.com/threerings/getdown + + https://github.com/threerings/getdown/issues + + + + + The (New) BSD License + http://www.opensource.org/licenses/bsd-license.php + repo + + + + + + samskivert + Michael Bayne + mdb@samskivert.com + + + + + scm:git:git://github.com/threerings/getdown.git + scm:git:git@github.com:threerings/getdown.git + https://github.com/threerings/getdown + + + + core + launcher + ant + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + false + + ossrh-releases + https://oss.sonatype.org/ + aa555c46fc37d0 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.7 + 1.7 + true + true + true + + -Xlint + -Xlint:-serial + -Xlint:-path + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.2 + + UTF-8 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0-M1 + + true + public + -Xdoclint:all -Xdoclint:-missing + + + + + + + + + eclipse + + + m2e.version + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + [1.0,) + + enforce + + + + + + + + + + + + + + + + + release-sign-artifacts + + performReleasetrue + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + mdb@samskivert.com + + + + + + + -- 1.7.10.2