+++ /dev/null
-Subproject commit 0e2f11698da3c6b9ffd9ce7a1317ff42b96a1dbf
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>getdown</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
--- /dev/null
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
--- /dev/null
+language: java
+sudo: false
+script: "mvn -B clean verify"
+
+cache:
+ directories:
+ - '$HOME/.m2/repository'
+
+jdk:
+ - openjdk7
+ - oraclejdk8
--- /dev/null
+#
+# 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 <mdb@samskivert.com>
+Ray Greenwell <ray@threerings.net>
--- /dev/null
+# 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
--- /dev/null
+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.
--- /dev/null
+## 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
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>getdown-ant</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding/<project>=UTF-8
--- /dev/null
+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
--- /dev/null
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.threerings.getdown</groupId>
+ <artifactId>getdown</artifactId>
+ <version>1.8.3-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>getdown-ant</artifactId>
+ <packaging>jar</packaging>
+ <name>Getdown Ant Task</name>
+ <description>An Ant task for building Getdown app distributions</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.threerings.getdown</groupId>
+ <artifactId>getdown-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.ant</groupId>
+ <artifactId>ant</artifactId>
+ <version>1.7.1</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+//
+// 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 <code>digest.txt</code> 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;
+}
--- /dev/null
+#Generated by Maven
+#Fri Apr 05 14:07:51 BST 2019
+version=1.8.3-SNAPSHOT
+groupId=com.threerings.getdown
+artifactId=getdown-ant
--- /dev/null
+com/threerings/getdown/tools/DigesterTask.class
--- /dev/null
+/Users/bsoares/git/getdown2/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java
--- /dev/null
+#!/bin/sh
+
+# we'll magically try to match dist/getdown.jar or target/getdown-1.x-SNAPSHOT.jar
+java -classpath */getdown*.jar com.threerings.getdown.tools.Differ "$@"
--- /dev/null
+#!/bin/sh
+
+# we'll magically try to match dist/getdown.jar or target/getdown-1.x-SNAPSHOT.jar
+java -classpath */getdown*.jar com.threerings.getdown.tools.Patcher "$@"
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>getdown-core</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+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/<project>=UTF-8
--- /dev/null
+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
--- /dev/null
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.threerings.getdown</groupId>
+ <artifactId>getdown</artifactId>
+ <version>1.8.3-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>getdown-core</artifactId>
+ <packaging>jar</packaging>
+ <name>Getdown Core</name>
+ <description>Core Getdown functionality</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>2.22.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <!-- 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 this
+ property on the command line, e.g. -Dgetdown.host.whitelist=my.server.com
+ Wildcards can be used (*.mycompany.com) and multiple values can be
+ separated by commas (app1.foo.com,app2.bar.com,app3.baz.com). -->
+ <properties>
+ <getdown.host.whitelist />
+ </properties>
+
+ <build>
+ <resources>
+ <resource> <!-- include the LICENSE file in the jar -->
+ <directory>..</directory>
+ <includes><include>LICENSE</include></includes>
+ </resource>
+ </resources>
+
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>1.5</version>
+ <executions>
+ <execution>
+ <id>add-test-source</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>add-test-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>src/it/java</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.8</version>
+ <executions>
+ <execution>
+ <id>gen-build</id>
+ <phase>generate-sources</phase>
+ <configuration>
+ <target>
+ <tstamp>
+ <format property="getdown.build.time" pattern="yyyy-MM-dd HH:mm" />
+ </tstamp>
+ <copy file="${project.build.sourceDirectory}/com/threerings/getdown/data/Build.java.tmpl" tofile="${project.build.sourceDirectory}/com/threerings/getdown/data/Build.java" overwrite="true">
+ <filterset>
+ <filter token="build_time" value="${getdown.build.time}" />
+ <filter token="build_version" value="${project.version}" />
+ <filter token="host_whitelist" value="${getdown.host.whitelist}" />
+ </filterset>
+ </copy>
+ </target>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>3.1.0</version>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>${project.build.sourceDirectory}/</directory>
+ <includes>
+ <include>com/threerings/getdown/data/Build.java</include>
+ </includes>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>2.22.0</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <useFile>false</useFile>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+//
+// 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<String> digestLines = Files.readAllLines(digest, StandardCharsets.UTF_8);
+ Files.delete(digest);
+
+ Path digest2 = appdir.resolve("digest2.txt");
+ List<String> 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);
+ }
+}
--- /dev/null
+Hello crazy world.
--- /dev/null
+# 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
--- /dev/null
+#!/bin/sh
+
+echo "Hello world!"
--- /dev/null
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.*;
+
+/**
+ * A placeholder class that contains a reference to the log object used by the Getdown code.
+ */
+public class Log
+{
+ public static class Shim {
+ /**
+ * Logs a debug message.
+ *
+ * @param message the message to be logged.
+ * @param args a list of key/value pairs and an optional final Throwable.
+ */
+ public void debug (Object message, Object... args) { doLog(0, message, args); }
+
+ /**
+ * Logs an info message.
+ *
+ * @param message the message to be logged.
+ * @param args a list of key/value pairs and an optional final Throwable.
+ */
+ public void info (Object message, Object... args) { doLog(1, message, args); }
+
+ /**
+ * Logs a warning message.
+ *
+ * @param message the message to be logged.
+ * @param args a list of key/value pairs and an optional final Throwable.
+ */
+ public void warning (Object message, Object... args) { doLog(2, message, args); }
+
+ /**
+ * Logs an error message.
+ *
+ * @param message the message to be logged.
+ * @param args a list of key/value pairs and an optional final Throwable.
+ */
+ public void error (Object message, Object... args) { doLog(3, message, args); }
+
+ protected void doLog (int levIdx, Object message, Object[] args) {
+ if (_impl.isLoggable(LEVELS[levIdx])) {
+ Throwable err = null;
+ int nn = args.length;
+ if (message instanceof Throwable) {
+ err = (Throwable)message;
+ } else if (nn % 2 == 1 && (args[nn - 1] instanceof Throwable)) {
+ err = (Throwable)args[--nn];
+ }
+ _impl.log(LEVELS[levIdx], format(message, args), err);
+ }
+ }
+
+ protected final Logger _impl = Logger.getLogger("com.threerings.getdown");
+ }
+
+ /** We dispatch our log messages through this logging shim. */
+ public static final Shim log = new Shim();
+
+ public static String format (Object message, Object... args) {
+ if (args.length < 2) return String.valueOf(message);
+ StringBuilder buf = new StringBuilder(String.valueOf(message));
+ if (buf.length() > 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("<toString() failure: ").append(t).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};
+}
--- /dev/null
+//
+// 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(".")));
+ }
+}
--- /dev/null
+//
+// 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";
+}
--- /dev/null
+//
+// 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 <code>getdown.txt</code>
+ * 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<Integer> 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<String> 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<String> 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<Step, List<Integer>> 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<Step, List<Integer>> 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<Resource> codes;
+ public final List<Resource> rsrcs;
+
+ public AuxGroup (String name, List<Resource> codes, List<Resource> 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 <code>getdown.txt</code>
+ * 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<Resource> getCodeResources ()
+ {
+ return _codes;
+ }
+
+ /**
+ * Returns a list of the non-code {@link Resource} objects used by this application.
+ */
+ public List<Resource> 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<Resource> getAllActiveResources ()
+ {
+ List<Resource> 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<AuxGroup> 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<Resource> getActiveCodeResources ()
+ {
+ ArrayList<Resource> 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<Resource> getNativeResources ()
+ {
+ List<Resource> 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<Resource> getActiveResources ()
+ {
+ ArrayList<Resource> 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<String, Object> 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<Resource> codes = new ArrayList<>();
+ parseResources(config, auxgroup + ".code", Resource.NORMAL, codes);
+ parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes);
+ ArrayList<Resource> 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<String> collector)
+ {
+ File pairFile = getLocalPath(pairLocation);
+ if (pairFile.exists()) {
+ try {
+ List<String[]> 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 <code>getdown.txt</code> 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 <code>getdown.txt</code> and <code>digest.txt</code> 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<String> 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<Object, Object> 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<String> 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<String> envAssignments = new ArrayList<>();
+ for (String assignment : envvar) {
+ envAssignments.add(processArg(assignment));
+ }
+ for (Map.Entry<String, String> 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<String, String> passProps = new HashMap<>();
+ for (Map.Entry<Object, Object> 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<String, String> 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 <code>digest.txt</code> file and verifies the contents of both that file and the
+ * <code>getdown.text</code> file. Then it loads the <code>version.txt</code> 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<Resource> unpacked,
+ Set<Resource> toInstall, Set<Resource> 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<Runnable> actions = new LinkedBlockingQueue<Runnable>();
+ final int[] completed = new int[1];
+
+ long start = System.currentTimeMillis();
+
+ // obtain the sizes of the resources to validate
+ List<Resource> 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<Resource> toInstallAsync = new ConcurrentSkipListSet<>(toInstall);
+ final Set<Resource> toDownloadAsync = new ConcurrentSkipListSet<>();
+ final Set<Resource> 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<Resource> unpacked,
+ Set<Resource> toInstall, Set<Resource> 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<Resource> unpacked)
+ throws InterruptedException
+ {
+ List<Resource> rsrcs = getActiveResources();
+
+ // remove resources that we don't want to unpack
+ for (Iterator<Resource> 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<Resource> 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.
+ *
+ * <p> 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<Resource.Attr> 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<String> target) {
+ if (values != null) {
+ for (String value : values) {
+ target.add(value);
+ }
+ }
+ }
+
+ /**
+ * Make an immutable List from the specified int array.
+ */
+ public static List<Integer> intsToList (int[] values)
+ {
+ List<Integer> 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<String> 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<Resource.Attr> attrs,
+ List<Resource> 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<Integer> _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<Resource> _codes = new ArrayList<>();
+ protected List<Resource> _resources = new ArrayList<>();
+
+ protected boolean _useCodeCache;
+ protected int _codeCacheRetentionDays;
+
+ protected Map<String,AuxGroup> _auxgroups = new HashMap<>();
+ protected Map<String,Boolean> _auxactive = new HashMap<>();
+
+ protected List<String> _jvmargs = new ArrayList<>();
+ protected List<String> _appargs = new ArrayList<>();
+
+ protected String[] _optimumJvmArgs;
+
+ protected List<String> _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\\.(.*?)%");
+}
--- /dev/null
+//
+// 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";
+ }
+
+ /**
+ * <p>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.
+ *
+ * <p>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<String> hostWhitelist () {
+ return Arrays.asList(StringUtil.parseStringArray(""));
+ }
+}
--- /dev/null
+//
+// 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@";
+ }
+
+ /**
+ * <p>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.
+ *
+ * <p>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<String> hostWhitelist () {
+ return Arrays.asList(StringUtil.parseStringArray("@host_whitelist@"));
+ }
+}
--- /dev/null
+//
+// 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<File> classPathEntries)
+ {
+ _classPathEntries = Collections.unmodifiableSet(classPathEntries);
+ }
+
+ /**
+ * Returns the class path as an java command line argument string, e.g.
+ *
+ * <pre>
+ * /path/to/a.jar:/path/to/b.jar
+ * </pre>
+ */
+ 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<File> 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<File> _classPathEntries;
+}
--- /dev/null
+//
+// 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 <code>digest.txt</code> 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<Resource> resources, File output)
+ throws IOException
+ {
+ // first compute the digests for all the resources in parallel
+ ExecutorService exec = Executors.newFixedThreadPool(SysProps.threadPoolSize());
+ final Map<Resource, String> digests = new ConcurrentHashMap<>();
+ final BlockingQueue<Object> completed = new LinkedBlockingQueue<>();
+ final int fversion = version;
+
+ long start = System.currentTimeMillis();
+
+ Set<Resource> 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<String, String> _digests = new HashMap<>();
+ protected String _metaDigest = "";
+
+ protected static final String FILE_NAME = "digest";
+ protected static final String FILE_SUFFIX = ".txt";
+}
--- /dev/null
+//
+// 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:
+ *
+ * <ul>
+ * <li> A {@code bootstrap.properties} file bundled with the jar. </li>
+ * <li> System properties supplied to the JVM. </li>
+ * <li> The supplied command line arguments ({@code argv}). </li>
+ * </ul>
+ *
+ * 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<Note> 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<String> appArgs = argv.length > 2 ?
+ Arrays.asList(argv).subList(2, argv.length) :
+ Collections.<String>emptyList();
+
+ // load X.509 certificate if it exists
+ File crtFile = new File(appDirFile, Digest.digestFile(Digest.VERSION) + ".crt");
+ List<Certificate> 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<Certificate> certs;
+
+ /** Additional arguments to pass on to launched application. These will be added after the
+ * args in the getdown.txt file. */
+ public final List<String> appArgs;
+
+ public EnvConfig (File appDir) {
+ this(appDir, null, null, Collections.<Certificate>emptyList(),
+ Collections.<String>emptyList());
+ }
+
+ private EnvConfig (File appDir, String appId, String appBase, List<Certificate> certs,
+ List<String> 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}";
+}
--- /dev/null
+//
+// 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<File> classPathEntries = new LinkedHashSet<File>();
+ 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<File> 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<Resource> resources = app.getNativeResources();
+ if (resources.isEmpty()) {
+ return null;
+ }
+
+ LinkedHashSet<File> 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);
+ }
+}
--- /dev/null
+//
+// 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";
+}
--- /dev/null
+//
+// 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<Resource>
+{
+ /** 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<Attr> NORMAL = EnumSet.noneOf(Attr.class);
+ public static final EnumSet<Attr> UNPACK = EnumSet.of(Attr.UNPACK);
+ public static final EnumSet<Attr> EXEC = EnumSet.of(Attr.EXEC);
+ public static final EnumSet<Attr> PRELOAD = EnumSet.of(Attr.PRELOAD);
+ public static final EnumSet<Attr> 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<JarEntry> 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<Attr> 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.
+ * <em>Note:</em> 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<Attr> _attrs;
+ protected boolean _isJar, _isPacked200Jar;
+
+ /** Used to sort the entries in a jar file. */
+ protected static final Comparator<JarEntry> ENTRY_COMP = new Comparator<JarEntry>() {
+ @Override public int compare (JarEntry e1, JarEntry e2) {
+ return e1.getName().compareTo(e2.getName());
+ }
+ };
+
+ protected static final int DIGEST_BUFFER_SIZE = 5 * 1025;
+}
--- /dev/null
+//
+// 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.
+ *
+ * <p>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}.
+ *
+ * <p>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.
+ *
+ * <p>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}.
+ *
+ * <p>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;
+ }
+}
--- /dev/null
+//
+// 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. <em>Note:</em> 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<Resource> 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<Long> 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<Resource, Long> _sizes = new HashMap<>();
+
+ /** The bytes downloaded for each resource. */
+ protected Map<Resource, Long> _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;
+}
--- /dev/null
+//
+// 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;
+}
--- /dev/null
+//
+// 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);
+}
--- /dev/null
+//
+// 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 <code>nvdir</code> directory with name
+ * <code>patchV.dat</code> 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<Resource> orsrcs = new ArrayList<>();
+ orsrcs.addAll(oapp.getCodeResources());
+ orsrcs.addAll(oapp.getResources());
+
+ Application napp = new Application(new EnvConfig(nvdir));
+ napp.init(false);
+ ArrayList<Resource> 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<Resource> orsrcs,
+ ArrayList<Resource> 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);
+ }
+ }
+}
--- /dev/null
+//
+// 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<Resource> 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));
+ }
+ }
+}
--- /dev/null
+//
+// 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.
+ *
+ * <p> 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 <code>os</code>.
+ */
+ 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<String,String> moved = new HashMap<>();
+ HashSet<String> implicit = new HashSet<>();
+ HashSet<String> moveSrc = new HashSet<>();
+ HashSet<String> 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 <oldname> <oldname>'
+ 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: <deleted files> = <oldjarnames> - <implicitmoves> -
+ // <source of move commands> - <new or modified entries>
+ ArrayList<String> 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<String,String> 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 <code>jos</code>.
+ * <code>oldEntries</code> gives the names of the files that were removed,
+ * <code>movedMap</code> maps from the new name to the old name.
+ */
+ private static void createIndex (JarOutputStream jos, List<String> oldEntries,
+ Map<String,String> 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<JarEntry>, Closeable
+ {
+ private JarFile _jar;
+ private List<JarEntry> _entries;
+ private HashMap<String,JarEntry> _nameToEntryMap;
+ private HashMap<Long,LinkedList<JarEntry>> _crcToEntryMap;
+
+ public JarFile2 (String path) throws IOException {
+ _jar = new JarFile(new File(path));
+ index();
+ }
+
+ public JarFile getJarFile () {
+ return _jar;
+ }
+
+ // from interface Iterable<JarEntry>
+ @Override
+ public Iterator<JarEntry> 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<JarEntry> ll = _crcToEntryMap.get(crcL);
+ // go through the list and check for content match
+ ListIterator<JarEntry> 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<JarEntry> 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<JarEntry> ll = _crcToEntryMap.get(crcL);
+ ll.add(entry);
+ _crcToEntryMap.put(crcL, ll);
+
+ } else {
+ // create a new entry in the hashmap for the new key
+ LinkedList<JarEntry> ll = new LinkedList<JarEntry>();
+ ll.add(entry);
+ _crcToEntryMap.put(crcL, ll);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ _jar.close();
+ }
+ }
+}
--- /dev/null
+//
+// 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";
+}
--- /dev/null
+//
+// 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<String> ignoreSet = new HashSet<>();
+ Map<String, String> 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<String> oldjarNames = new HashSet<>();
+ Enumeration<JarEntry> 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<JarEntry> 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 <oldName> <newName> 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<String> 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<String> ignoreSet, Map<String, String> 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<String> 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<String> 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<String> getSubpaths (String path)
+ {
+ int index = 0;
+ int length = path.length();
+ ArrayList<String> 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];
+}
--- /dev/null
+//
+// 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. <em>Note:</em> 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.
+ *
+ * <p><em>Note:</em> 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<JarEntry> 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;
+}
--- /dev/null
+/*
+ * 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 <a
+ * href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
+ * href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
+ */
+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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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
+ * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>).
+ */
+ 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
+}
--- /dev/null
+//
+// 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 () {}
+}
--- /dev/null
+//
+// 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<String, Object>());
+
+ /** 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 <code>String[]</code> instances containing the key/value pairs in the
+ * order they were parsed from the file.
+ */
+ public static List<String[]> 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<String[]> parsePairs (Reader source, ParseOpts opts) throws IOException
+ {
+ List<String[]> 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<String, Object> data = new HashMap<>();
+
+ // I thought that we could use HashMap<String, String[]> and put new String[] {pair[1]} for
+ // the null case, but it mysteriously dies on launch, so leaving it as HashMap<String,
+ // Object> 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<String, Object> 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:
+ * <pre>
+ * id = os[-arch]
+ * ids = id | id,ids
+ * quals = !id | ids
+ * </pre>
+ * 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<String, Object> _data;
+}
--- /dev/null
+//
+// 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);
+ }
+}
--- /dev/null
+//
+// 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<String> readLines (Reader in)
+ throws IOException
+ {
+ List<String> 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<File> 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);
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+//
+// 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<String> 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);
+ }
+}
--- /dev/null
+//
+// 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 <code>version.txt</code> 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 <code>getdown-pro.jar</code> or <code>getdown-retro-pro.jar</code> 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 <code>version.txt</code> 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 <java.home>/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).
+ *
+ * <p> 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
+ }
+ }
+}
--- /dev/null
+//
+// 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 <code>MessageFormat</code>.
+ * As we assume all single quotes are to be escaped, we cannot use the characters
+ * <code>{</code> and <code>}</code> 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
+ * <code>MessageFormat</code> 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 = "~";
+}
--- /dev/null
+//
+// 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;
+}
--- /dev/null
+//
+// 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);
+}
--- /dev/null
+//
+// 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 + "]";
+ }
+}
--- /dev/null
+//
+// 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 extends OutputStream> 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();
+ }
+}
--- /dev/null
+//
+// 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:
+ *
+ * <pre>25, 17, 21, 99</pre>
+ *
+ * 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:
+ *
+ * <pre>mary, had, a, little, lamb, and, an, escaped, comma,,</pre>
+ *
+ * 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 <code>null</code> 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 <code>join</code> 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";
+}
--- /dev/null
+//
+// 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;
+ }
+}
--- /dev/null
+//
+// 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;
+}
--- /dev/null
+//
+// 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);
+}
--- /dev/null
+//
+// 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<File> classPathEntries = new LinkedHashSet<File>();
+ 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;
+}
--- /dev/null
+//
+// 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<EnvConfig.Note> notes) {
+ for (EnvConfig.Note note : notes) {
+ System.out.println(note.message);
+ }
+ }
+
+ private void checkNoNotes (List<EnvConfig.Note> 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<EnvConfig.Note> 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<EnvConfig.Note> 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<EnvConfig.Note> 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<EnvConfig.Note> 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<EnvConfig.Note> 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<EnvConfig.Note> 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<EnvConfig.Note> 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);
+ }
+}
--- /dev/null
+//
+// 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();
+}
--- /dev/null
+//
+// 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));
+ }
+ }
+}
--- /dev/null
+//
+// 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);
+ }
+}
--- /dev/null
+//
+// 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<String[]> 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<String[]> 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<String[]> 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();
+}
--- /dev/null
+//
+// 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<String> 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();
+}
--- /dev/null
+//
+// 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<String> 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);
+ }
+}
--- /dev/null
+//
+// 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\""));
+ }
+}
--- /dev/null
+//
+// 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);
+ }
+}
--- /dev/null
+mock-maker-inline
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<project name="maven-antrun-" default="main" >
+<target name="main">
+ <tstamp>
+ <format property="getdown.build.time" pattern="yyyy-MM-dd HH:mm"/>
+ </tstamp>
+ <copy file="/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl" tofile="/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Build.java" overwrite="true">
+ <filterset>
+ <filter value="${getdown.build.time}" token="build_time"/>
+ <filter value="1.8.3-SNAPSHOT" token="build_version"/>
+ <filter value="" token="host_whitelist"/>
+ </filterset>
+ </copy>
+</target>
+</project>
\ No newline at end of file
--- /dev/null
+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.
--- /dev/null
+#Generated by Maven
+#Fri Apr 05 14:07:47 BST 2019
+version=1.8.3-SNAPSHOT
+groupId=com.threerings.getdown
+artifactId=getdown-core
--- /dev/null
+com/threerings/getdown/util/StringUtil.class
+com/threerings/getdown/util/LaunchUtil.class
+com/threerings/getdown/tools/JarDiff.class
+com/threerings/getdown/data/Application$2$1.class
+com/threerings/getdown/tools/Patcher$1.class
+com/threerings/getdown/data/Application$StatusDisplay.class
+com/threerings/getdown/util/Color.class
+com/threerings/getdown/Log$OneLineFormatter.class
+com/threerings/getdown/tools/Patcher.class
+com/threerings/getdown/spi/ProxyAuth.class
+com/threerings/getdown/data/EnvConfig.class
+com/threerings/getdown/tools/Differ.class
+com/threerings/getdown/net/Downloader$State.class
+com/threerings/getdown/data/Application$3.class
+com/threerings/getdown/util/StreamUtil.class
+com/threerings/getdown/util/Config$ParseOpts.class
+com/threerings/getdown/net/Downloader$1.class
+com/threerings/getdown/tools/JarDiffCodes.class
+com/threerings/getdown/data/EnvConfig$Note$Level.class
+com/threerings/getdown/util/HostWhitelist.class
+com/threerings/getdown/util/Base64$Coder.class
+com/threerings/getdown/Log.class
+com/threerings/getdown/data/Digest$1.class
+com/threerings/getdown/data/Application.class
+com/threerings/getdown/data/Properties.class
+com/threerings/getdown/data/Application$AuxGroup.class
+com/threerings/getdown/util/VersionUtil.class
+com/threerings/getdown/util/Base64$Decoder.class
+com/threerings/getdown/data/Resource$Attr.class
+com/threerings/getdown/data/Resource$1.class
+com/threerings/getdown/data/Build.class
+com/threerings/getdown/util/Rectangle.class
+com/threerings/getdown/util/ConnectionUtil.class
+com/threerings/getdown/cache/GarbageCollector.class
+com/threerings/getdown/util/Base64$Encoder.class
+com/threerings/getdown/tools/JarDiff$JarFile2.class
+com/threerings/getdown/util/Base64.class
+com/threerings/getdown/data/ClassPath.class
+com/threerings/getdown/util/FileUtil$Visitor.class
+com/threerings/getdown/data/Application$2.class
+com/threerings/getdown/Log$Shim.class
+com/threerings/getdown/data/Resource.class
+com/threerings/getdown/data/SysProps.class
+com/threerings/getdown/data/Digest.class
+com/threerings/getdown/tools/JarDiffPatcher.class
+com/threerings/getdown/cache/GarbageCollector$1.class
+com/threerings/getdown/data/Application$3$1.class
+com/threerings/getdown/util/ProgressAggregator$1.class
+com/threerings/getdown/data/Application$UpdateInterface$Step.class
+com/threerings/getdown/data/PathBuilder.class
+com/threerings/getdown/util/MessageUtil.class
+com/threerings/getdown/util/Config.class
+com/threerings/getdown/spi/ProxyAuth$Credentials.class
+com/threerings/getdown/net/Downloader.class
+com/threerings/getdown/data/EnvConfig$Note.class
+com/threerings/getdown/util/ProgressObserver.class
+com/threerings/getdown/tools/Digester.class
+com/threerings/getdown/cache/ResourceCache.class
+com/threerings/getdown/util/FileUtil.class
+com/threerings/getdown/net/HTTPDownloader.class
+com/threerings/getdown/data/Application$UpdateInterface.class
+com/threerings/getdown/util/ProgressAggregator.class
+com/threerings/getdown/data/Application$1.class
--- /dev/null
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Application.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/Log.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/Color.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Build.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/Config.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java
+/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java
--- /dev/null
+com/threerings/getdown/util/FileUtilTest.class
+com/threerings/getdown/util/ColorTest.class
+com/threerings/getdown/util/HostWhitelistTest.class
+com/threerings/getdown/data/ClassPathTest.class
+com/threerings/getdown/data/EnvConfigTest.class
+com/threerings/getdown/util/ConfigTest.class
+com/threerings/getdown/data/PathBuilderTest.class
+com/threerings/getdown/util/ConfigTest$Pair.class
+com/threerings/getdown/util/StringUtilTest.class
+com/threerings/getdown/cache/ResourceCacheTest.class
+com/threerings/getdown/cache/GarbageCollectorTest.class
+com/threerings/getdown/util/VersionUtilTest.class
+com/threerings/getdown/tests/DigesterIT.class
+com/threerings/getdown/util/FileUtilTest$1CountingVisitor.class
+com/threerings/getdown/data/SysPropsTest.class
--- /dev/null
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java
+/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="5" failures="0" name="com.threerings.getdown.cache.GarbageCollectorTest" time="0.023" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.cache.GarbageCollectorTest" name="shouldDeleteCachedFileIfLastAccessedFileIsMissing" time="0.019"/>
+ <testcase classname="com.threerings.getdown.cache.GarbageCollectorTest" name="shouldDeleteCacheFolderIfFolderIsEmpty" time="0"/>
+ <testcase classname="com.threerings.getdown.cache.GarbageCollectorTest" name="shouldDeleteLastAccessedFileIfCachedFileIsMissing" time="0.001"/>
+ <testcase classname="com.threerings.getdown.cache.GarbageCollectorTest" name="shouldDeleteCacheEntryIfRetentionPeriodIsReached" time="0.001"/>
+ <testcase classname="com.threerings.getdown.cache.GarbageCollectorTest" name="shouldKeepFilesInCacheIfRententionPeriodIsNotReached" time="0.002"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="4" failures="0" name="com.threerings.getdown.cache.ResourceCacheTest" time="0.002" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.cache.ResourceCacheTest" name="shouldCacheFile" time="0"/>
+ <testcase classname="com.threerings.getdown.cache.ResourceCacheTest" name="shouldTrackFileUsage" time="0"/>
+ <testcase classname="com.threerings.getdown.cache.ResourceCacheTest" name="shouldNotCacheTheSameFile" time="0"/>
+ <testcase classname="com.threerings.getdown.cache.ResourceCacheTest" name="shouldRememberWhenFileWasRequested" time="0.002"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="2" failures="0" name="com.threerings.getdown.data.ClassPathTest" time="0.003" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.data.ClassPathTest" name="shouldProvideJarUrls" time="0.002"/>
+ <testcase classname="com.threerings.getdown.data.ClassPathTest" name="shouldCreateValidArgumentString" time="0.001"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="6" failures="0" name="com.threerings.getdown.data.EnvConfigTest" time="0.006" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.data.EnvConfigTest" name="testSysPropsDir" time="0.004"/>
+ <testcase classname="com.threerings.getdown.data.EnvConfigTest" name="testArgvDirId" time="0"/>
+ <testcase classname="com.threerings.getdown.data.EnvConfigTest" name="testArgvDir" time="0"/>
+ <testcase classname="com.threerings.getdown.data.EnvConfigTest" name="testSysPropsDirIdBase" time="0.001"/>
+ <testcase classname="com.threerings.getdown.data.EnvConfigTest" name="testArgvDirIdArgs" time="0.001"/>
+ <testcase classname="com.threerings.getdown.data.EnvConfigTest" name="testArgvDirArgs" time="0"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="2" failures="0" name="com.threerings.getdown.data.PathBuilderTest" time="0.051" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.data.PathBuilderTest" name="shouldBuildCachedClassPath" time="0.048"/>
+ <testcase classname="com.threerings.getdown.data.PathBuilderTest" name="shouldBuildDefaultClassPath" time="0.003"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="3" failures="0" name="com.threerings.getdown.data.SysPropsTest" time="0.002" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.data.SysPropsTest" name="testAppbaseOverride" time="0.001"/>
+ <testcase classname="com.threerings.getdown.data.SysPropsTest" name="testAppbaseDomain" time="0.001"/>
+ <testcase classname="com.threerings.getdown.data.SysPropsTest" name="testStartDelay" time="0"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="1" failures="0" name="com.threerings.getdown.util.ColorTest" time="0" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.util.ColorTest" name="testBrightness" time="0"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="2" failures="0" name="com.threerings.getdown.util.ConfigTest" time="0.017" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.util.ConfigTest" name="testSimplePairs" time="0.001"/>
+ <testcase classname="com.threerings.getdown.util.ConfigTest" name="testQualifiedPairs" time="0.016"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="3" failures="0" name="com.threerings.getdown.util.FileUtilTest" time="0.004" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.util.FileUtilTest" name="shouldCopyFile" time="0.001"/>
+ <testcase classname="com.threerings.getdown.util.FileUtilTest" name="testReadLines" time="0.001"/>
+ <testcase classname="com.threerings.getdown.util.FileUtilTest" name="shouldRecursivelyWalkOverFilesAndFolders" time="0.002"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="1" failures="0" name="com.threerings.getdown.util.HostWhitelistTest" time="0.009" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.util.HostWhitelistTest" name="testVerify" time="0.009"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="1" failures="0" name="com.threerings.getdown.util.StringUtilTest" time="0" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.util.StringUtilTest" name="testCouldBeValidUrl" time="0"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<testsuite tests="5" failures="0" name="com.threerings.getdown.util.VersionUtilTest" time="0.001" errors="0" skipped="0">
+ <properties>
+ <property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
+ <property name="sun.boot.library.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib"/>
+ <property name="java.vm.version" value="25.202-b08"/>
+ <property name="gopherProxySet" value="false"/>
+ <property name="java.vm.vendor" value="Oracle Corporation"/>
+ <property name="maven.multiModuleProjectDirectory" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.vendor.url" value="https://adoptopenjdk.net/"/>
+ <property name="path.separator" value=":"/>
+ <property name="guice.disable.misplaced.annotation.check" value="true"/>
+ <property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
+ <property name="file.encoding.pkg" value="sun.io"/>
+ <property name="user.country" value="GB"/>
+ <property name="sun.java.launcher" value="SUN_STANDARD"/>
+ <property name="sun.os.patch.level" value="unknown"/>
+ <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
+ <property name="user.dir" value="/Users/bsoares/git/getdown2/getdown"/>
+ <property name="java.runtime.version" value="1.8.0_202-b08"/>
+ <property name="java.awt.graphicsenv" value="sun.awt.CGraphicsEnvironment"/>
+ <property name="java.endorsed.dirs" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/endorsed"/>
+ <property name="os.arch" value="x86_64"/>
+ <property name="java.io.tmpdir" value="/var/folders/l1/hnbhx1t55lx82wctsg09z0jwc62nf3/T/"/>
+ <property name="line.separator" value="
+"/>
+ <property name="java.vm.specification.vendor" value="Oracle Corporation"/>
+ <property name="os.name" value="Mac OS X"/>
+ <property name="classworlds.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/bin/m2.conf"/>
+ <property name="sun.jnu.encoding" value="UTF-8"/>
+ <property name="java.library.path" value="/Users/bsoares/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
+ <property name="maven.conf" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/conf"/>
+ <property name="java.specification.name" value="Java Platform API Specification"/>
+ <property name="java.class.version" value="52.0"/>
+ <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
+ <property name="os.version" value="10.13.6"/>
+ <property name="library.jansi.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/lib/jansi-native"/>
+ <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="user.home" value="/Users/bsoares"/>
+ <property name="user.timezone" value="Europe/London"/>
+ <property name="java.awt.printerjob" value="sun.lwawt.macosx.CPrinterJob"/>
+ <property name="java.specification.version" value="1.8"/>
+ <property name="file.encoding" value="UTF-8"/>
+ <property name="user.name" value="bsoares"/>
+ <property name="java.class.path" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2/boot/plexus-classworlds-2.5.2.jar"/>
+ <property name="java.vm.specification.version" value="1.8"/>
+ <property name="sun.arch.data.model" value="64"/>
+ <property name="java.home" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre"/>
+ <property name="sun.java.command" value="org.codehaus.plexus.classworlds.launcher.Launcher package"/>
+ <property name="java.specification.vendor" value="Oracle Corporation"/>
+ <property name="user.language" value="en"/>
+ <property name="awt.toolkit" value="sun.lwawt.macosx.LWCToolkit"/>
+ <property name="java.vm.info" value="mixed mode"/>
+ <property name="java.version" value="1.8.0_202"/>
+ <property name="java.ext.dirs" value="/Users/bsoares/Library/Java/Extensions:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/>
+ <property name="sun.boot.class.path" value="/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/resources.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/rt.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/sunrsasign.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jsse.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jce.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/charsets.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/lib/jfr.jar:/Users/bsoares/buildtools/jvm/OpenJDK8/OpenJDK8U-jdk_x64_mac_hotspot_8u202b08/jdk8u202-b08/Contents/Home/jre/classes"/>
+ <property name="java.vendor" value="AdoptOpenJdk"/>
+ <property name="maven.home" value="/Users/bsoares/.mvnvm/apache-maven-3.5.2"/>
+ <property name="file.separator" value="/"/>
+ <property name="java.vendor.url.bug" value="https://github.com/AdoptOpenJDK/openjdk-build/issues"/>
+ <property name="sun.cpu.endian" value="little"/>
+ <property name="sun.io.unicode.encoding" value="UnicodeBig"/>
+ <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
+ <property name="sun.cpu.isalist" value=""/>
+ </properties>
+ <testcase classname="com.threerings.getdown.util.VersionUtilTest" name="shouldParseJavaRuntimeVersion" time="0.001"/>
+ <testcase classname="com.threerings.getdown.util.VersionUtilTest" name="shouldParseJavaVersion10" time="0"/>
+ <testcase classname="com.threerings.getdown.util.VersionUtilTest" name="shouldParseJavaVersion8" time="0"/>
+ <testcase classname="com.threerings.getdown.util.VersionUtilTest" name="shouldParseJavaVersion9" time="0"/>
+ <testcase classname="com.threerings.getdown.util.VersionUtilTest" name="shouldParseJavaVersion" time="0"/>
+</testsuite>
\ No newline at end of file
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.cache.GarbageCollectorTest
+-------------------------------------------------------------------------------
+Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.074 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.cache.ResourceCacheTest
+-------------------------------------------------------------------------------
+Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.data.ClassPathTest
+-------------------------------------------------------------------------------
+Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.data.EnvConfigTest
+-------------------------------------------------------------------------------
+Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.data.PathBuilderTest
+-------------------------------------------------------------------------------
+Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.804 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.data.SysPropsTest
+-------------------------------------------------------------------------------
+Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.util.ColorTest
+-------------------------------------------------------------------------------
+Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.util.ConfigTest
+-------------------------------------------------------------------------------
+Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.util.FileUtilTest
+-------------------------------------------------------------------------------
+Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.util.HostWhitelistTest
+-------------------------------------------------------------------------------
+Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.01 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.util.StringUtilTest
+-------------------------------------------------------------------------------
+Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec
--- /dev/null
+-------------------------------------------------------------------------------
+Test set: com.threerings.getdown.util.VersionUtilTest
+-------------------------------------------------------------------------------
+Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec
--- /dev/null
+mock-maker-inline
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>getdown-launcher</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding/<project>=UTF-8
--- /dev/null
+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
--- /dev/null
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.threerings.getdown</groupId>
+ <artifactId>getdown</artifactId>
+ <version>1.8.3-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>getdown-launcher</artifactId>
+ <packaging>jar</packaging>
+ <name>Getdown Launcher</name>
+ <description>The Getdown app updater/launcher</description>
+
+ <repositories>
+ <repository>
+ <id>lib-repo</id>
+ <url>file://${basedir}/../lib</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.threerings.getdown</groupId>
+ <artifactId>getdown-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.samskivert</groupId>
+ <artifactId>samskivert</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>jregistrykey</groupId>
+ <artifactId>jregistrykey</artifactId>
+ <version>1.0</version>
+ <optional>true</optional>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.github.wvengen</groupId>
+ <artifactId>proguard-maven-plugin</artifactId>
+ <version>2.0.14</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals><goal>proguard</goal></goals>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.proguard</groupId>
+ <artifactId>proguard-base</artifactId>
+ <version>6.0.3</version>
+ <scope>runtime</scope>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <proguardVersion>6.0.3</proguardVersion>
+ <outputDirectory>${project.build.directory}</outputDirectory>
+ <outjar>${project.build.finalName}.jar</outjar>
+ <injar>${project.build.finalName}.jar</injar>
+ <assembly>
+ <inclusions>
+ <inclusion>
+ <groupId>com.threerings.getdown</groupId>
+ <artifactId>getdown-core</artifactId>
+ </inclusion>
+ <inclusion>
+ <groupId>com.samskivert</groupId>
+ <artifactId>samskivert</artifactId>
+ <filter>
+ !**/*.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/**
+ </filter>
+ </inclusion>
+ <inclusion>
+ <groupId>jregistrykey</groupId>
+ <artifactId>jregistrykey</artifactId>
+ </inclusion>
+ </inclusions>
+ </assembly>
+ <obfuscate>true</obfuscate>
+ <options>
+ <option>-keep public class com.threerings.getdown.** { *; }</option>
+ <option>-keep public class ca.beq.util.win32.registry.** { *; }</option>
+ <option>-keepattributes Exceptions, InnerClasses, Signature</option>
+ </options>
+ <libs>
+ <lib>${rt.jar.path}</lib>
+ </libs>
+ <addMavenDescriptor>false</addMavenDescriptor>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.1.0</version>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>com.threerings.getdown.launcher.GetdownApp</mainClass>
+ </manifest>
+ <manifestEntries>
+ <Permissions>all-permissions</Permissions>
+ <Application-Name>Getdown</Application-Name>
+ <Codebase>*</Codebase>
+ <Application-Library-Allowable-Codebase>*</Application-Library-Allowable-Codebase>
+ <Caller-Allowable-Codebase>*</Caller-Allowable-Codebase>
+ <Trusted-Library>true</Trusted-Library>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <!-- finagling to find rt.jar -->
+ <profile>
+ <id>non-mac-jre</id>
+ <activation>
+ <file><exists>${java.home}/../lib/rt.jar</exists></file>
+ </activation>
+ <properties>
+ <rt.jar.path>${java.home}/../lib/rt.jar</rt.jar.path>
+ </properties>
+ </profile>
+ <profile>
+ <id>non-mac-jdk</id>
+ <activation>
+ <file><exists>${java.home}/lib/rt.jar</exists></file>
+ </activation>
+ <properties>
+ <rt.jar.path>${java.home}/lib/rt.jar</rt.jar.path>
+ </properties>
+ </profile>
+ <profile>
+ <id>java-9-jdk</id>
+ <activation>
+ <file><exists>${java.home}/jmods/java.base.jmod</exists></file>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.github.wvengen</groupId>
+ <artifactId>proguard-maven-plugin</artifactId>
+ <configuration>
+ <libs>
+ <lib>${java.home}/jmods/java.base.jmod</lib>
+ <lib>${java.home}/jmods/java.desktop.jmod</lib>
+ <lib>${java.home}/jmods/java.logging.jmod</lib>
+ <lib>${java.home}/jmods/jdk.jsobject.jmod</lib>
+ </libs>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
--- /dev/null
+//
+// 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;
+}
--- /dev/null
+//
+// 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<Resource> resources) {
+ List<Resource> 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<Resource> 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<Resource> 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 _<language>} 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<Resource> 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<Resource> 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<Resource> 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 <code>message</code>.
+ */
+ 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<Resource> _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;
+}
--- /dev/null
+//
+// 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<EnvConfig.Note> 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<Image> 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;
+ }
+}
--- /dev/null
+//
+// 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");
+ }
+
+}
--- /dev/null
+//
+// 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;
+}
--- /dev/null
+//
+// 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<ProxyAuth> loader = ServiceLoader.load(ProxyAuth.class);
+ Iterator<ProxyAuth> 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<ProxyAuth> loader = ServiceLoader.load(ProxyAuth.class);
+ Iterator<ProxyAuth> 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";
+}
--- /dev/null
+//
+// 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 <code>backgrounds</code>.
+ *
+ * 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<String> 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;
+}
--- /dev/null
+//
+// 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);
+}
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Abort installation?
+m.abort_confirm = <html>Are you sure you want to stop installation? \
+ You can resume at a later time by running the application again.</html>
+m.abort_ok = Quit
+m.abort_cancel = Continue installation
+
+m.detecting_proxy = Trying to auto-detect proxy settings
+
+m.configure_proxy = <html>We were unable to connect to the application server to download data. \
+ <p> Please make sure that no virus scanner or firewall is blocking network communicaton with \
+ the server. \
+ <p> 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.</html>
+
+m.proxy_extra = <html>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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Installation abbrechen?
+m.abort_confirm = <html>Bist du sicher, dass du die Installation abbrechen \
+m\u00f6chtest? \
+ Du kannst sp\u00e4ter fortfahren, indem du die Anwendung erneut \
+ausf\u00fchrst.</html>
+m.abort_ok = Beenden
+m.abort_cancel = Installation fortsetzen
+
+m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln
+
+m.configure_proxy = <html>Es konnte keine Verbindung zum Applikations-Server aufgebaut werden. \
+ <p>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.<br> \
+ Wenn kein Proxy verwendet werden soll, l\u00f6schen Sie bitte alle Eintr\u00e4ge in den unten \
+ stehenden Feldern und klicken sie auf OK.</html>
+
+m.proxy_extra = <html>Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \
+ an Ihren Administrator.</html>
+
+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.
+
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = \u00bfCancelar la instalaci\u00f3n?
+m.abort_confirm = <html>\u00bfEst\u00e1s seguro de querer cancelar la instalaci\u00f3n? \
+ Puedes continuarla despu\u00e9s si corres de nuevo la aplicaci\u00f3n.</html>
+m.abort_ok = Cancelar
+m.abort_cancel = Continuar la instalaci\u00f3n
+
+m.detecting_proxy = Detectando autom\u00e1ticamente la configuraci\u00f3n proxy
+
+m.configure_proxy = <html>No ha sido posible conectar con nuestros servidores para \
+ descargar los datos del juego. \
+ <ul><li> Si el cortafuegos de Windows o Norton Internet Security tiene instrucciones \
+ de bloquear <code>javaw.exe</code> no podemos descargar el juego. Necesitar\u00e1s \
+ permitir que <code>javaw.exe</code> 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 ).</ul> \
+ <p> 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.</html>
+
+m.proxy_extra = <html>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.</html>
+
+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.
+
--- /dev/null
+#
+# $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 =<html>\u00cates-vous s\u00fbr de vouloir annuler l'installation? \
+ Vous pourrez reprendre l'installation en ex\u00e9cutant l'application de nouveau.</html>
+m.abort_ok = Quitter
+m.abort_cancel = Continuer l'installation
+
+m.detecting_proxy = D\u00e9tection automatique des r\u00e9glages proxy
+
+m.configure_proxy =<html>Connexion au serveur impossible. \
+ <ul><li> Veuillez v\u00e9rifier que <code>javaw.exe</code> 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 ).</ul> \
+ <p> 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.</html>
+
+m.proxy_extra =<html>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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Annullare l'installazione?
+m.abort_confirm = <html>Sei sicuro di voler annullare l'installazione? \
+ Potrai riprenderla in seguito, riavviando nuovamente l'applicazione.</html>
+m.abort_ok = Chiudi
+m.abort_cancel = Continua l'installazione
+
+m.detecting_proxy = Provo a recuperare le configurazioni del proxy
+
+m.configure_proxy = <html>Impossibile collegarsi al server per \
+ recuperare i dati. \
+ <ul><li> Se il Firewall di Windows o Norton Internet Security bloccano \
+ <code>javaw.exe</code> non si possono scaricare i dati. Devi \
+ permettere a <code>javaw.exe</code> di accedere a internet. Puoi provare \
+ di nuovo, ma dovresti abilitare javaw.exe nella tua configurazione \
+ del firewall ( Start -> Pannello di Controllo -> Windows Firewall ).</ul> \
+ <p> 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.</html>
+
+m.proxy_extra = <html>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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f
+m.abort_confirm = <html>\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</html>
+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 = <html>\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 \
+ <ul><li>\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 \
+ <code>javaw.exe</code>\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 \
+ <code>javaw.exe</code>\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</ul> \
+ <p>\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</html>
+
+m.proxy_extra = <html>\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</html>
+
+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
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?
+m.abort_confirm = <html>\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.</html>
+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 = <html>\uAC8C\uC784 \uB370\uC774\uD130\uB97C \uBC1B\uAE30 \uC704\uD55C \uC11C\uBC84 \uC811\uC18D\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4.\
+ <ul><li>\uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD \uB610\uB294 \uB178\uD134 \uC778\uD130\uB137 \uC2DC\uD050\uB9AC\uD2F0\uAC00 <code>javaw.exe</code>\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. \
+ <code>javaw.exe</code>\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 )</ul> \
+ <p> \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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Cancelar a instala\u00E7\u00E3o?
+m.abort_confirm = <html>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.</html>
+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 = <html>N\u00E3o foi poss\u00EDvel conectar aos nossos servidores para \
+ fazer o download dos dados. \
+ <ul><li> Se o Firewall do Windows ou o Norton Internet Security est\u00E1 configurado \
+ para bloquear o programa <code>javaw.exe</code> n\u00E3o ser\u00E1 poss\u00EDvel realizar \
+ o download. Voc\u00EA ter\u00E1 que permitir que o programa <code>javaw.exe</code> 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).</ul> \
+ <p> 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.</html>
+
+m.proxy_extra = <html>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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.detecting_proxy = \u641c\u5bfb\u4ee3\u7406\u670d\u52a1\u5668
+
+m.configure_proxy = <html>\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</html>
+
+m.proxy_extra = <html>\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<br><br> \
+ \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</html>
+
+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
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Abort installation?
+m.abort_confirm = <html>Are you sure you want to stop installation? \
+ You can resume at a later time by running the application again.</html>
+m.abort_ok = Quit
+m.abort_cancel = Continue installation
+
+m.detecting_proxy = Trying to auto-detect proxy settings
+
+m.configure_proxy = <html>We were unable to connect to the application server to download data. \
+ <p> Please make sure that no virus scanner or firewall is blocking network communicaton with \
+ the server. \
+ <p> 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.</html>
+
+m.proxy_extra = <html>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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Installation abbrechen?
+m.abort_confirm = <html>Bist du sicher, dass du die Installation abbrechen \
+m\u00f6chtest? \
+ Du kannst sp\u00e4ter fortfahren, indem du die Anwendung erneut \
+ausf\u00fchrst.</html>
+m.abort_ok = Beenden
+m.abort_cancel = Installation fortsetzen
+
+m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln
+
+m.configure_proxy = <html>Es konnte keine Verbindung zum Applikations-Server aufgebaut werden. \
+ <p>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.<br> \
+ Wenn kein Proxy verwendet werden soll, l\u00f6schen Sie bitte alle Eintr\u00e4ge in den unten \
+ stehenden Feldern und klicken sie auf OK.</html>
+
+m.proxy_extra = <html>Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \
+ an Ihren Administrator.</html>
+
+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.
+
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = \u00bfCancelar la instalaci\u00f3n?
+m.abort_confirm = <html>\u00bfEst\u00e1s seguro de querer cancelar la instalaci\u00f3n? \
+ Puedes continuarla despu\u00e9s si corres de nuevo la aplicaci\u00f3n.</html>
+m.abort_ok = Cancelar
+m.abort_cancel = Continuar la instalaci\u00f3n
+
+m.detecting_proxy = Detectando autom\u00e1ticamente la configuraci\u00f3n proxy
+
+m.configure_proxy = <html>No ha sido posible conectar con nuestros servidores para \
+ descargar los datos del juego. \
+ <ul><li> Si el cortafuegos de Windows o Norton Internet Security tiene instrucciones \
+ de bloquear <code>javaw.exe</code> no podemos descargar el juego. Necesitar\u00e1s \
+ permitir que <code>javaw.exe</code> 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 ).</ul> \
+ <p> 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.</html>
+
+m.proxy_extra = <html>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.</html>
+
+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.
+
--- /dev/null
+#
+# $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 =<html>\u00cates-vous s\u00fbr de vouloir annuler l'installation? \
+ Vous pourrez reprendre l'installation en ex\u00e9cutant l'application de nouveau.</html>
+m.abort_ok = Quitter
+m.abort_cancel = Continuer l'installation
+
+m.detecting_proxy = D\u00e9tection automatique des r\u00e9glages proxy
+
+m.configure_proxy =<html>Connexion au serveur impossible. \
+ <ul><li> Veuillez v\u00e9rifier que <code>javaw.exe</code> 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 ).</ul> \
+ <p> 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.</html>
+
+m.proxy_extra =<html>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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Annullare l'installazione?
+m.abort_confirm = <html>Sei sicuro di voler annullare l'installazione? \
+ Potrai riprenderla in seguito, riavviando nuovamente l'applicazione.</html>
+m.abort_ok = Chiudi
+m.abort_cancel = Continua l'installazione
+
+m.detecting_proxy = Provo a recuperare le configurazioni del proxy
+
+m.configure_proxy = <html>Impossibile collegarsi al server per \
+ recuperare i dati. \
+ <ul><li> Se il Firewall di Windows o Norton Internet Security bloccano \
+ <code>javaw.exe</code> non si possono scaricare i dati. Devi \
+ permettere a <code>javaw.exe</code> di accedere a internet. Puoi provare \
+ di nuovo, ma dovresti abilitare javaw.exe nella tua configurazione \
+ del firewall ( Start -> Pannello di Controllo -> Windows Firewall ).</ul> \
+ <p> 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.</html>
+
+m.proxy_extra = <html>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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f
+m.abort_confirm = <html>\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</html>
+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 = <html>\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 \
+ <ul><li>\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 \
+ <code>javaw.exe</code>\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 \
+ <code>javaw.exe</code>\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</ul> \
+ <p>\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</html>
+
+m.proxy_extra = <html>\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</html>
+
+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
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?
+m.abort_confirm = <html>\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.</html>
+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 = <html>\uAC8C\uC784 \uB370\uC774\uD130\uB97C \uBC1B\uAE30 \uC704\uD55C \uC11C\uBC84 \uC811\uC18D\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4.\
+ <ul><li>\uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD \uB610\uB294 \uB178\uD134 \uC778\uD130\uB137 \uC2DC\uD050\uB9AC\uD2F0\uAC00 <code>javaw.exe</code>\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. \
+ <code>javaw.exe</code>\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 )</ul> \
+ <p> \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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.abort_title = Cancelar a instala\u00E7\u00E3o?
+m.abort_confirm = <html>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.</html>
+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 = <html>N\u00E3o foi poss\u00EDvel conectar aos nossos servidores para \
+ fazer o download dos dados. \
+ <ul><li> Se o Firewall do Windows ou o Norton Internet Security est\u00E1 configurado \
+ para bloquear o programa <code>javaw.exe</code> n\u00E3o ser\u00E1 poss\u00EDvel realizar \
+ o download. Voc\u00EA ter\u00E1 que permitir que o programa <code>javaw.exe</code> 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).</ul> \
+ <p> 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.</html>
+
+m.proxy_extra = <html>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.</html>
+
+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.
--- /dev/null
+#
+# $Id$
+#
+# Getdown translation messages
+
+m.detecting_proxy = \u641c\u5bfb\u4ee3\u7406\u670d\u52a1\u5668
+
+m.configure_proxy = <html>\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</html>
+
+m.proxy_extra = <html>\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<br><br> \
+ \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</html>
+
+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
--- /dev/null
+#Created by Apache Maven 3.5.2
+version=1.8.3-SNAPSHOT
+groupId=com.threerings.getdown
+artifactId=getdown-launcher
--- /dev/null
+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
--- /dev/null
+/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
--- /dev/null
+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 <init>(ca.beq.util.win32.registry.RegistryKey) -> <init>
+ 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 <init>() -> <init>
+ void <init>(java.lang.String) -> <init>
+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 <init>() -> <init>
+ void <init>(ca.beq.util.win32.registry.RootKey) -> <init>
+ void <init>(java.lang.String) -> <init>
+ void <init>(ca.beq.util.win32.registry.RootKey,java.lang.String) -> <init>
+ 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 <clinit>() -> <clinit>
+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 <init>() -> <init>
+ void <init>(java.lang.Object) -> <init>
+ void <init>(java.lang.String,java.lang.Object) -> <init>
+ void <init>(java.lang.String,ca.beq.util.win32.registry.ValueType,java.lang.Object) -> <init>
+ void <init>(java.lang.String,boolean) -> <init>
+ void <init>(java.lang.String,byte) -> <init>
+ void <init>(java.lang.String,int) -> <init>
+ void <init>(java.lang.String,long) -> <init>
+ void <init>(java.lang.String,float) -> <init>
+ void <init>(java.lang.String,double) -> <init>
+ 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 <init>(java.lang.String,int) -> <init>
+ int getValue() -> getValue
+ java.lang.String toString() -> toString
+ void <clinit>() -> <clinit>
+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 <init>(ca.beq.util.win32.registry.RegistryKey) -> <init>
+ 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 <init>(java.lang.String,int) -> <init>
+ int getValue() -> getValue
+ java.lang.String toString() -> toString
+ void <clinit>() -> <clinit>
+com.samskivert.Log -> com.a.a:
+ com.samskivert.util.Logger log -> a
+ void <clinit>() -> <clinit>
+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 <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <clinit>() -> <clinit>
+com.samskivert.swing.GroupLayout$Constraints -> com.a.a.b$a:
+ int _weight -> a
+ void <init>(int) -> <init>
+ boolean isFixed() -> a
+ int getWeight() -> b
+com.samskivert.swing.GroupLayout$Justification -> com.a.a.b$b:
+ void <init>() -> <init>
+com.samskivert.swing.GroupLayout$Policy -> com.a.a.b$c:
+ void <init>() -> <init>
+com.samskivert.swing.HGroupLayout -> com.a.a.c:
+ void <init>(com.samskivert.swing.GroupLayout$Policy,com.samskivert.swing.GroupLayout$Justification) -> <init>
+ void <init>() -> <init>
+ 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 <init>() -> <init>
+ void <init>(java.lang.String) -> <init>
+ void <init>(java.lang.String,java.awt.Color,java.awt.Font) -> <init>
+ void <init>(java.lang.String,int,java.awt.Color,java.awt.Color,java.awt.Font) -> <init>
+ 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 <clinit>() -> <clinit>
+com.samskivert.swing.Spacer -> com.a.a.e:
+ void <init>(int,int) -> <init>
+ void <init>(java.awt.Dimension) -> <init>
+com.samskivert.swing.VGroupLayout -> com.a.a.f:
+ void <init>() -> <init>
+ 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 <clinit>() -> <clinit>
+com.samskivert.util.AbstractIntSet -> com.a.b.a:
+ void <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <clinit>() -> <clinit>
+com.samskivert.util.FormatterUtil -> com.a.b.d:
+ java.lang.String LINE_SEPARATOR -> a
+ void configureDefaultHandler(java.util.logging.Formatter) -> a
+ void <clinit>() -> <clinit>
+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 <init>(int,float) -> <init>
+ void <init>() -> <init>
+ 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 <init>(com.samskivert.util.HashIntMap) -> <init>
+ 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 <init>(com.samskivert.util.HashIntMap) -> <init>
+ 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 <init>(com.samskivert.util.HashIntMap) -> <init>
+ 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 <init>(com.samskivert.util.HashIntMap$3) -> <init>
+ 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 <init>(com.samskivert.util.HashIntMap) -> <init>
+ java.lang.Object next() -> next
+com.samskivert.util.HashIntMap$MapEntryIterator -> com.a.b.e$b:
+ com.samskivert.util.HashIntMap this$0 -> a
+ void <init>(com.samskivert.util.HashIntMap) -> <init>
+ 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 <init>(int,java.lang.Object) -> <init>
+ 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 <init>(com.samskivert.util.HashIntMap) -> <init>
+ 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 <init>() -> <init>
+ 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 <init>(java.util.logging.Logger) -> <init>
+ boolean shouldLog(int) -> a
+ void doLog(int,java.lang.String,java.lang.Throwable) -> a
+ void <clinit>() -> <clinit>
+com.samskivert.util.Logger -> com.a.b.o:
+ com.samskivert.util.Logger$Factory _factory -> a
+ void <init>() -> <init>
+ 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 <clinit>() -> <clinit>
+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 <init>() -> <init>
+ void <init>(boolean) -> <init>
+ 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 <clinit>() -> <clinit>
+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 <clinit>() -> <clinit>
+com.samskivert.util.Throttle -> com.a.b.s:
+ long[] _ops -> a
+ int _lastOp -> b
+ long _period -> c
+ void <init>(int,long) -> <init>
+ 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 <init>(java.lang.Object,java.lang.Object) -> <init>
+ 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 <init>() -> <init>
+ java.lang.String format(java.lang.Object,java.lang.Object[]) -> format
+ void <clinit>() -> <clinit>
+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 <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <init>(long) -> <init>
+ 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 <init>(java.io.File) -> <init>
+ 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 <init>(com.threerings.getdown.data.EnvConfig) -> <init>
+ 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 <clinit>() -> <clinit>
+com.threerings.getdown.data.Application$1 -> com.threerings.getdown.data.a:
+ com.threerings.getdown.data.Application this$0 -> a
+ void <init>(com.threerings.getdown.data.Application,java.net.URL[],java.lang.ClassLoader) -> <init>
+ 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 <init>(com.threerings.getdown.data.Application,java.util.concurrent.BlockingQueue,com.threerings.getdown.util.ProgressObserver) -> <init>
+ 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 <init>(com.threerings.getdown.data.Application$2,int) -> <init>
+ 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 <init>(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[]) -> <init>
+ 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 <init>(com.threerings.getdown.data.Application$3) -> <init>
+ 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 <init>(java.lang.String,java.util.List,java.util.List) -> <init>
+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 <init>(com.threerings.getdown.util.Config) -> <init>
+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 <init>(java.lang.String,int,int[]) -> <init>
+ void <clinit>() -> <clinit>
+com.threerings.getdown.data.Build -> com.threerings.getdown.data.Build:
+ void <init>() -> <init>
+ 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 <init>(java.util.LinkedHashSet) -> <init>
+ 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 <init>(java.io.File,boolean) -> <init>
+ void <init>(java.io.File,int,boolean) -> <init>
+ 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 <init>(int,java.util.Map,com.threerings.getdown.data.Resource,java.util.concurrent.BlockingQueue) -> <init>
+ 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 <init>(java.io.File) -> <init>
+ void <init>(java.io.File,java.lang.String,java.lang.String,java.util.List,java.util.List) -> <init>
+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 <init>(com.threerings.getdown.data.EnvConfig$Note$Level,java.lang.String) -> <init>
+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 <init>(java.lang.String,int) -> <init>
+ void <clinit>() -> <clinit>
+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 <init>() -> <init>
+ 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 <init>() -> <init>
+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 <init>(java.lang.String,java.net.URL,java.io.File,java.util.EnumSet) -> <init>
+ 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 <clinit>() -> <clinit>
+com.threerings.getdown.data.Resource$1 -> com.threerings.getdown.data.g:
+ void <init>() -> <init>
+ 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 <init>(java.lang.String,int) -> <init>
+ void <clinit>() -> <clinit>
+com.threerings.getdown.data.SysProps -> com.threerings.getdown.data.SysProps:
+ void <init>() -> <init>
+ 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 <init>(com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) -> <init>
+ 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 <init>(com.threerings.getdown.data.EnvConfig) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.Getdown) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.Getdown,java.net.Proxy) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.Getdown,java.io.InputStream) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.Getdown,boolean) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.Getdown$4,java.lang.String) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.Getdown,java.lang.String,int,long) -> <init>
+ void run() -> run
+com.threerings.getdown.launcher.Getdown$6 -> com.threerings.getdown.launcher.g:
+ com.threerings.getdown.launcher.Getdown this$0 -> a
+ void <init>(com.threerings.getdown.launcher.Getdown) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.Getdown,java.net.URL) -> <init>
+ void run() -> run
+com.threerings.getdown.launcher.GetdownApp -> com.threerings.getdown.launcher.GetdownApp:
+ void <init>() -> <init>
+ 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 <init>(com.threerings.getdown.data.EnvConfig) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.GetdownApp$1) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.GetdownApp$1) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.GetdownApp$1) -> <init>
+ 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 <clinit>() -> <clinit>
+com.threerings.getdown.launcher.MultipleGetdownRunning -> com.threerings.getdown.launcher.MultipleGetdownRunning:
+ void <init>() -> <init>
+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 <init>(com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) -> <init>
+ 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 <init>(com.threerings.getdown.launcher.ProxyPanel) -> <init>
+ void itemStateChanged(java.awt.event.ItemEvent) -> itemStateChanged
+com.threerings.getdown.launcher.ProxyPanel$SaneLabelField -> com.threerings.getdown.launcher.ProxyPanel$SaneLabelField:
+ void <init>(java.lang.String) -> <init>
+ java.awt.Dimension getPreferredSize() -> getPreferredSize
+com.threerings.getdown.launcher.ProxyPanel$SanePasswordField -> com.threerings.getdown.launcher.ProxyPanel$SanePasswordField:
+ void <init>() -> <init>
+ java.awt.Dimension getPreferredSize() -> getPreferredSize
+com.threerings.getdown.launcher.ProxyPanel$SaneTextField -> com.threerings.getdown.launcher.ProxyPanel$SaneTextField:
+ void <init>() -> <init>
+ java.awt.Dimension getPreferredSize() -> getPreferredSize
+com.threerings.getdown.launcher.ProxyUtil -> com.threerings.getdown.launcher.ProxyUtil:
+ java.lang.String PROXY_REGISTRY -> PROXY_REGISTRY
+ void <init>() -> <init>
+ 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 <init>(java.lang.String,char[]) -> <init>
+ 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 <init>() -> <init>
+ void <init>(java.awt.Image) -> <init>
+ void <init>(java.util.List,java.lang.String,com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader) -> <init>
+ 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 <init>(java.util.ResourceBundle) -> <init>
+ 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 <clinit>() -> <clinit>
+com.threerings.getdown.launcher.StatusPanel$1 -> com.threerings.getdown.launcher.o:
+ com.threerings.getdown.launcher.StatusPanel this$0 -> a
+ void <init>(com.threerings.getdown.launcher.StatusPanel) -> <init>
+ 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 <init>() -> <init>
+ 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 <init>(com.threerings.getdown.net.Downloader,com.threerings.getdown.data.Resource) -> <init>
+ 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 <init>(java.lang.String,int) -> <init>
+ void <clinit>() -> <clinit>
+com.threerings.getdown.net.HTTPDownloader -> com.threerings.getdown.net.HTTPDownloader:
+ java.net.Proxy _proxy -> _proxy
+ void <init>(java.net.Proxy) -> <init>
+ 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 <init>(java.lang.String,java.lang.String) -> <init>
+com.threerings.getdown.tools.Differ -> com.threerings.getdown.tools.Differ:
+ void <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <clinit>() -> <clinit>
+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 <init>(java.lang.String) -> <init>
+ 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 <init>() -> <init>
+ 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 <clinit>() -> <clinit>
+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 <init>() -> <init>
+ 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 <init>(com.threerings.getdown.tools.Patcher,long) -> <init>
+ 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 <init>() -> <init>
+ void <clinit>() -> <clinit>
+com.threerings.getdown.util.Base64$Coder -> com.threerings.getdown.util.Base64$a:
+ byte[] output -> a
+ int op -> b
+ void <init>() -> <init>
+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 <init>(int,byte[]) -> <init>
+ boolean process(byte[],int,int,boolean) -> a
+ void <clinit>() -> <clinit>
+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 <init>(int,byte[]) -> <init>
+ boolean process(byte[],int,int,boolean) -> a
+ void <clinit>() -> <clinit>
+com.threerings.getdown.util.Color -> com.threerings.getdown.util.Color:
+ int CLEAR -> CLEAR
+ int WHITE -> WHITE
+ int BLACK -> BLACK
+ float brightness(int) -> brightness
+ void <init>() -> <init>
+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 <init>(java.util.Map) -> <init>
+ 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 <clinit>() -> <clinit>
+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 <init>() -> <init>
+com.threerings.getdown.util.ConnectionUtil -> com.threerings.getdown.util.ConnectionUtil:
+ void <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <clinit>() -> <clinit>
+com.threerings.getdown.util.MessageUtil -> com.threerings.getdown.util.MessageUtil:
+ java.lang.String TAINT_CHAR -> TAINT_CHAR
+ void <init>() -> <init>
+ 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 <init>(com.threerings.getdown.util.ProgressObserver,long[]) -> <init>
+ 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 <init>(com.threerings.getdown.util.ProgressAggregator,int) -> <init>
+ 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 <init>(int,int,int,int) -> <init>
+ 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 <init>() -> <init>
+ 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 <init>() -> <init>
+ 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 <init>() -> <init>
+ 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
--- /dev/null
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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 <clinit>()
+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)
--- /dev/null
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>jregistrykey</groupId>
+ <artifactId>jregistrykey</artifactId>
+ <version>1.0</version>
+ <description>POM was created from install:install-file</description>
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<metadata>
+ <groupId>jregistrykey</groupId>
+ <artifactId>jregistrykey</artifactId>
+ <version>1.0</version>
+ <versioning>
+ <versions>
+ <version>1.0</version>
+ </versions>
+ <lastUpdated>20101118155146</lastUpdated>
+ </versioning>
+</metadata>
--- /dev/null
+Main-Class: com.threerings.getdown.launcher.Getdown
+Permissions: all-permissions
+Application-Name: Getdown
+Codebase: *
+Application-Library-Allowable-Codebase: *
+Caller-Allowable-Codebase: *
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.sonatype.oss</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>7</version>
+ </parent>
+
+ <groupId>com.threerings.getdown</groupId>
+ <artifactId>getdown</artifactId>
+ <packaging>pom</packaging>
+ <version>1.8.3-SNAPSHOT</version>
+
+ <name>getdown</name>
+ <description>An application installer and updater.</description>
+ <url>https://github.com/threerings/getdown</url>
+ <issueManagement>
+ <url>https://github.com/threerings/getdown/issues</url>
+ </issueManagement>
+
+ <licenses>
+ <license>
+ <name>The (New) BSD License</name>
+ <url>http://www.opensource.org/licenses/bsd-license.php</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <developers>
+ <developer>
+ <id>samskivert</id>
+ <name>Michael Bayne</name>
+ <email>mdb@samskivert.com</email>
+ </developer>
+ </developers>
+
+ <scm>
+ <connection>scm:git:git://github.com/threerings/getdown.git</connection>
+ <developerConnection>scm:git:git@github.com:threerings/getdown.git</developerConnection>
+ <url>https://github.com/threerings/getdown</url>
+ </scm>
+
+ <modules>
+ <module>core</module>
+ <module>launcher</module>
+ <module>ant</module>
+ </modules>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>nexus-staging-maven-plugin</artifactId>
+ <version>1.6.8</version>
+ <extensions>true</extensions>
+ <inherited>false</inherited>
+ <configuration>
+ <serverId>ossrh-releases</serverId>
+ <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+ <stagingProfileId>aa555c46fc37d0</stagingProfileId>
+ </configuration>
+ </plugin>
+ </plugins>
+
+ <!-- Common plugin configuration for all children -->
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.7.0</version>
+ <configuration>
+ <source>1.7</source>
+ <target>1.7</target>
+ <fork>true</fork>
+ <showDeprecation>true</showDeprecation>
+ <showWarnings>true</showWarnings>
+ <compilerArgs>
+ <arg>-Xlint</arg>
+ <arg>-Xlint:-serial</arg>
+ <arg>-Xlint:-path</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.0.2</version>
+ <configuration>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>3.0.0-M1</version>
+ <configuration>
+ <quiet>true</quiet>
+ <show>public</show>
+ <additionalparam>-Xdoclint:all -Xdoclint:-missing</additionalparam>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>eclipse</id>
+ <activation>
+ <property>
+ <name>m2e.version</name>
+ </property>
+ </activation>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <!-- Tell m2eclipse to ignore the enforcer plugin from our parent. Otherwise it warns
+ about not being able to run it. -->
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <versionRange>[1.0,)</versionRange>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore />
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+
+ <profile>
+ <id>release-sign-artifacts</id>
+ <activation>
+ <property><name>performRelease</name><value>true</value></property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.6</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <keyname>mdb@samskivert.com</keyname>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>