allow tasks being hidden in task window

This commit is contained in:
huangyuhui
2017-01-22 17:01:02 +08:00
parent db4615a2d3
commit 0d03e965d2
44 changed files with 278 additions and 306 deletions

View File

@@ -28,7 +28,7 @@ import org.jackhuang.hellominecraft.util.func.Consumer;
*
* @author huangyuhui
*/
public abstract class OverridableSwingWorker<T> extends SwingWorker<Void, T> {
public abstract class AbstractSwingWorker<T> extends SwingWorker<Void, T> {
List<Consumer<T>> processListeners = new ArrayList<>();
List<Runnable> doneListeners = new ArrayList<>();
@@ -47,13 +47,13 @@ public abstract class OverridableSwingWorker<T> extends SwingWorker<Void, T> {
return null;
}
public OverridableSwingWorker reg(Consumer<T> c) {
public AbstractSwingWorker reg(Consumer<T> c) {
Objects.requireNonNull(c);
processListeners.add(c);
return this;
}
public OverridableSwingWorker regDone(Runnable c) {
public AbstractSwingWorker regDone(Runnable c) {
Objects.requireNonNull(c);
doneListeners.add(c);
return this;

View File

@@ -31,7 +31,7 @@ public final class CollectionUtils {
private CollectionUtils() {
}
public static <T> ArrayList<T> map(Collection<T> coll, Predicate<T> p) {
public static <T> ArrayList<T> filter(Collection<T> coll, Predicate<T> p) {
ArrayList<T> newColl = new ArrayList<>();
for (T t : coll)
if (p.apply(t))

View File

@@ -26,47 +26,47 @@ import java.io.OutputStream;
*/
public class DoubleOutputStream extends OutputStream {
private OutputStream a = null;
private OutputStream b = null;
private boolean c = true;
private OutputStream os1 = null;
private OutputStream os2 = null;
private boolean autoFlush = true;
public DoubleOutputStream(OutputStream paramOutputStream1, OutputStream paramOutputStream2) {
this(paramOutputStream1, paramOutputStream2, true);
public DoubleOutputStream(OutputStream os1, OutputStream os2) {
this(os1, os2, true);
}
private DoubleOutputStream(OutputStream paramOutputStream1, OutputStream paramOutputStream2, boolean paramBoolean) {
this.a = paramOutputStream1;
this.b = paramOutputStream2;
this.c = true;
private DoubleOutputStream(OutputStream os1, OutputStream os2, boolean autoFlush) {
this.os1 = os1;
this.os2 = os2;
this.autoFlush = autoFlush;
}
@Override
public final void write(byte[] arr, int off, int len) throws IOException {
if (this.a != null)
this.a.write(arr, off, len);
if (this.b != null)
this.b.write(arr, off, len);
if (this.c)
if (this.os1 != null)
this.os1.write(arr, off, len);
if (this.os2 != null)
this.os2.write(arr, off, len);
if (this.autoFlush)
flush();
}
@Override
public final void write(byte[] arr) throws IOException {
if (this.a != null)
this.a.write(arr);
if (this.b != null)
this.b.write(arr);
if (this.c)
if (this.os1 != null)
this.os1.write(arr);
if (this.os2 != null)
this.os2.write(arr);
if (this.autoFlush)
flush();
}
@Override
public final void write(int i) throws IOException {
if (this.a != null)
this.a.write(i);
if (this.b != null)
this.b.write(i);
if (this.c)
if (this.os1 != null)
this.os1.write(i);
if (this.os2 != null)
this.os2.write(i);
if (this.autoFlush)
flush();
}
@@ -74,17 +74,17 @@ public class DoubleOutputStream extends OutputStream {
public final void close() throws IOException {
flush();
if (this.a != null)
this.a.close();
if (this.b != null)
this.b.close();
if (this.os1 != null)
this.os1.close();
if (this.os2 != null)
this.os2.close();
}
@Override
public final void flush() throws IOException {
if (this.a != null)
this.a.flush();
if (this.b != null)
this.b.flush();
if (this.os1 != null)
this.os1.flush();
if (this.os2 != null)
this.os2.flush();
}
}

View File

@@ -49,13 +49,13 @@ public interface IUpdateChecker {
*
* @return the process observable.
*/
OverridableSwingWorker<VersionNumber> process(boolean showMessage);
AbstractSwingWorker<VersionNumber> process(boolean showMessage);
/**
* Get the download links.
*
* @return a JSON, which contains the server response.
*/
OverridableSwingWorker<Map<String, String>> requestDownloadLink();
AbstractSwingWorker<Map<String, String>> requestDownloadLink();
}

View File

@@ -68,7 +68,7 @@ public class MinecraftVersionRequest implements Serializable {
private static MinecraftVersionRequest getVersionOfOldMinecraft(ZipFile file, ZipEntry entry) throws IOException {
MinecraftVersionRequest r = new MinecraftVersionRequest();
byte[] tmp = IOUtils.getBytesFromStream(file.getInputStream(entry));
byte[] tmp = IOUtils.readFully(file.getInputStream(entry)).toByteArray();
byte[] bytes = "Minecraft Minecraft ".getBytes("ASCII");
int j = ArrayUtils.matchArray(tmp, bytes);
@@ -92,7 +92,7 @@ public class MinecraftVersionRequest implements Serializable {
private static MinecraftVersionRequest getVersionOfNewMinecraft(ZipFile file, ZipEntry entry) throws IOException {
MinecraftVersionRequest r = new MinecraftVersionRequest();
byte[] tmp = IOUtils.getBytesFromStream(file.getInputStream(entry));
byte[] tmp = IOUtils.readFully(file.getInputStream(entry)).toByteArray();
byte[] str = "-server.txt".getBytes("ASCII");
int j = ArrayUtils.matchArray(tmp, str);
@@ -175,12 +175,7 @@ public class MinecraftVersionRequest implements Serializable {
r.type = MinecraftVersionRequest.INVALID_JAR;
return r;
} finally {
if (f != null)
try {
f.close();
} catch (IOException ex) {
HMCLog.warn("Failed to close zip file", ex);
}
IOUtils.closeQuietly(f);
}
}
}

View File

@@ -26,6 +26,7 @@ import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.util.Map;
import org.jackhuang.hellominecraft.util.code.Charsets;
import org.jackhuang.hellominecraft.util.system.IOUtils;
/**
@@ -33,12 +34,22 @@ import org.jackhuang.hellominecraft.util.system.IOUtils;
* @author huang
*/
public final class NetUtils {
private NetUtils() {
}
private static HttpURLConnection createConnection(URL url, Proxy proxy) throws IOException {
HttpURLConnection con = (HttpURLConnection) url.openConnection(proxy);
con.setDoOutput(true);
con.setDoInput(true);
con.setUseCaches(false);
con.setConnectTimeout(15000);
con.setReadTimeout(15000);
return con;
}
public static String get(String url, String encoding) throws IOException {
return IOUtils.getStreamContent(new URL(url).openConnection().getInputStream());
return IOUtils.toString(new URL(url).openConnection().getInputStream());
}
public static String get(String url) throws IOException {
@@ -50,7 +61,7 @@ public final class NetUtils {
}
public static String get(URL url, Proxy proxy) throws IOException {
return IOUtils.getStreamContent(url.openConnection(proxy).getInputStream());
return readData(createConnection(url, proxy));
}
public static String post(URL u, Map<String, String> params) throws IOException {
@@ -72,17 +83,13 @@ public final class NetUtils {
}
public static String post(URL u, String post, String contentType, Proxy proxy) throws IOException {
HttpURLConnection con = (HttpURLConnection) u.openConnection(proxy);
byte[] bytes = post.getBytes(Charsets.UTF_8);
HttpURLConnection con = createConnection(u, proxy);
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setDoInput(true);
con.setUseCaches(false);
con.setConnectTimeout(30000);
con.setReadTimeout(30000);
con.setRequestProperty("Content-Type", contentType + "; charset=utf-8");
byte[] bytes = post.getBytes(IOUtils.DEFAULT_CHARSET);
con.setRequestProperty("Content-Length", "" + bytes.length);
con.connect();
OutputStream os = null;
try {
os = con.getOutputStream();
@@ -90,20 +97,23 @@ public final class NetUtils {
} finally {
IOUtils.closeQuietly(os);
}
return readData(con);
}
String result;
private static String readData(HttpURLConnection con) throws IOException {
InputStream is = null;
try {
is = con.getInputStream();
result = IOUtils.getStreamContent(is);
} catch (IOException ex) {
return IOUtils.toString(is, Charsets.UTF_8);
} catch (IOException e) {
IOUtils.closeQuietly(is);
is = con.getErrorStream();
result = IOUtils.getStreamContent(is);
if (is != null)
return IOUtils.toString(is, Charsets.UTF_8);
throw e;
} finally {
IOUtils.closeQuietly(is);
}
con.disconnect();
return result;
}
public static URL constantURL(String url) {
@@ -117,9 +127,7 @@ public final class NetUtils {
public static URL concatenateURL(URL url, String query) {
try {
if (url.getQuery() != null && url.getQuery().length() > 0)
return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile() + "&" + query);
return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile() + "?" + query);
return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile() + (url.getQuery() != null && url.getQuery().length() > 0 ? '&' : '?') + query);
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Could not concatenate given URL with GET arguments!", ex);
}

View File

@@ -37,14 +37,6 @@ public final class StrUtils {
private StrUtils() {
}
public static String substring(String src, int start_idx, int end_idx) {
byte[] b = src.getBytes();
String tgt = "";
for (int i = start_idx; i <= end_idx; i++)
tgt += (char) b[i];
return tgt;
}
public static String makeCommand(List<String> cmd) {
StringBuilder cmdbuf = new StringBuilder(120);
for (int i = 0; i < cmd.size(); i++) {
@@ -189,7 +181,7 @@ public final class StrUtils {
public static boolean equals(String base, String to) {
if (base == null)
return (to == null);
return to == null;
else
return base.equals(to);
}
@@ -220,15 +212,6 @@ public final class StrUtils {
return (String[]) localArrayList.toArray(new String[localArrayList.size()]);
}
public static String trimExtension(String filename) {
if ((filename != null) && (filename.length() > 0)) {
int i = filename.lastIndexOf('.');
if ((i > -1) && (i < (filename.length())))
return filename.substring(0, i);
}
return filename;
}
public static boolean isBlank(String s) {
return s == null || s.trim().length() <= 0;
}

View File

@@ -28,7 +28,7 @@ import java.util.Map;
*/
public final class UpdateChecker implements IUpdateChecker {
public boolean OUT_DATED = false;
private volatile boolean outOfDate = false;
public VersionNumber base;
public String versionString;
public String type;
@@ -41,9 +41,13 @@ public final class UpdateChecker implements IUpdateChecker {
VersionNumber value;
public boolean isOutOfDate() {
return outOfDate;
}
@Override
public OverridableSwingWorker<VersionNumber> process(final boolean showMessage) {
return new OverridableSwingWorker() {
public AbstractSwingWorker<VersionNumber> process(final boolean showMessage) {
return new AbstractSwingWorker() {
@Override
protected void work() throws Exception {
if (value == null) {
@@ -56,8 +60,8 @@ public final class UpdateChecker implements IUpdateChecker {
if (showMessage)
MessageBox.show(C.i18n("update.failed"));
} else if (VersionNumber.isOlder(base, value))
OUT_DATED = true;
if (OUT_DATED)
outOfDate = true;
if (outOfDate)
publish(value);
}
};
@@ -69,8 +73,8 @@ public final class UpdateChecker implements IUpdateChecker {
}
@Override
public synchronized OverridableSwingWorker<Map<String, String>> requestDownloadLink() {
return new OverridableSwingWorker() {
public synchronized AbstractSwingWorker<Map<String, String>> requestDownloadLink() {
return new AbstractSwingWorker() {
@Override
protected void work() throws Exception {
if (download_link == null)
@@ -84,11 +88,11 @@ public final class UpdateChecker implements IUpdateChecker {
};
}
public final EventHandler<VersionNumber> outdated = new EventHandler<>(this);
public final EventHandler<VersionNumber> outOfDateEvent = new EventHandler<>(this);
@Override
public void checkOutdate() {
if (OUT_DATED)
outdated.execute(getNewVersion());
if (outOfDate)
outOfDateEvent.execute(getNewVersion());
}
}

View File

@@ -36,13 +36,17 @@ public final class Localization {
private static final Map<Locale, Localization> INSTANCE = new HashMap<>();
private final Map<String, String> lang;
private static InputStream getStream(String id) {
return Localization.class.getResourceAsStream(String.format(ROOT_LOCATION, id));
}
private Localization(Locale locale) {
InputStream is = Localization.class.getResourceAsStream(String.format(ROOT_LOCATION, "_" + locale.getLanguage() + "_" + locale.getCountry()));
InputStream is = getStream("_" + locale.getLanguage() + "_" + locale.getCountry());
if (is == null)
is = Localization.class.getResourceAsStream(String.format(ROOT_LOCATION, "_" + locale.getLanguage()));
is = getStream("_" + locale.getLanguage());
if (is == null)
is = Localization.class.getResourceAsStream(String.format(ROOT_LOCATION, ""));
is = getStream("");
if (is == null)
throw new RuntimeException("LANG FILE MISSING");

View File

@@ -195,12 +195,12 @@ public final class FileUtils {
public static String read(File file)
throws IOException {
return IOUtils.getStreamContent(IOUtils.openInputStream(file));
return IOUtils.toString(IOUtils.openInputStream(file));
}
public static String readQuietly(File file) {
try {
return IOUtils.getStreamContent(IOUtils.openInputStream(file));
return IOUtils.toString(IOUtils.openInputStream(file));
} catch (IOException ex) {
HMCLog.err("Failed to read file: " + file, ex);
return null;
@@ -209,12 +209,12 @@ public final class FileUtils {
public static String read(File file, String charset)
throws IOException {
return IOUtils.getStreamContent(IOUtils.openInputStream(file), charset);
return IOUtils.toString(IOUtils.openInputStream(file), charset);
}
public static String readIgnoreFileNotFound(File file) throws IOException {
try {
return IOUtils.getStreamContent(IOUtils.openInputStream(file));
return IOUtils.toString(IOUtils.openInputStream(file));
} catch (FileNotFoundException ex) {
return "";
}
@@ -263,14 +263,6 @@ public final class FileUtils {
return Math.max(lastUnixPos, lastWindowsPos);
}
public static int indexOfExtension(String filename) {
if (filename == null)
return -1;
int extensionPos = filename.lastIndexOf(46);
int lastSeparator = indexOfLastSeparator(filename);
return lastSeparator > extensionPos ? -1 : extensionPos;
}
public static String getName(String filename) {
if (filename == null)
return null;
@@ -289,6 +281,14 @@ public final class FileUtils {
return removeExtension(getName(filename));
}
public static int indexOfExtension(String filename) {
if (filename == null)
return -1;
int extensionPos = filename.lastIndexOf(46);
int lastSeparator = indexOfLastSeparator(filename);
return lastSeparator > extensionPos ? -1 : extensionPos;
}
public static String getExtension(String filename) {
if (filename == null)
return null;
@@ -333,7 +333,6 @@ public final class FileUtils {
try {
out = openOutputStream(file, append);
IOUtils.write(data, out, encoding);
out.close();
} finally {
IOUtils.closeQuietly(out);
}

View File

@@ -202,6 +202,22 @@ public final class IOUtils {
return entryBuffer;
}
public static byte[] toByteArray(InputStream stream) throws IOException {
return readFully(stream).toByteArray();
}
public static String toString(InputStream is) throws IOException {
return readFully(is).toString();
}
public static String toString(InputStream is, String charset) throws IOException {
return readFully(is).toString(charset);
}
public static String toString(InputStream is, Charset charset) throws IOException {
return readFully(is).toString(charset.name());
}
public static void closeQuietly(Closeable closeable) {
try {
@@ -302,31 +318,6 @@ public final class IOUtils {
output.write(buf, 0, length);
}
public static byte[] getBytesFromStream(InputStream is) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copyStream(is, out);
is.close();
return out.toByteArray();
}
public static String getStreamContent(InputStream is) throws IOException {
return getStreamContent(is, DEFAULT_CHARSET);
}
public static String getStreamContent(InputStream is, String encoding)
throws IOException {
if (is == null)
return null;
StringBuilder sb = new StringBuilder();
try (InputStreamReader br = new InputStreamReader(is, encoding)) {
int len;
char[] buf = new char[16384];
while ((len = br.read(buf)) != -1)
sb.append(buf, 0, len);
}
return sb.toString();
}
public static final String DEFAULT_CHARSET = "UTF-8";
public static PrintStream createPrintStream(OutputStream out, Charset charset) {

View File

@@ -78,9 +78,8 @@ public class JavaProcessMonitor {
al.add(a);
}
void processThreadStopped(ProcessThread t, boolean forceTermintate) {
al.remove(t);
al.removeAll(CollectionUtils.map(al, t1 -> !t1.isAlive()));
void processThreadStopped(ProcessThread t1, boolean forceTermintate) {
CollectionUtils.removeIf(al, t -> t == t1 || !t.isAlive());
if (al.isEmpty() || forceTermintate) {
for (Thread a : al)
a.interrupt();

View File

@@ -17,6 +17,7 @@
*/
package org.jackhuang.hellominecraft.util.system;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -46,35 +47,29 @@ public class ProcessThread extends Thread {
@Override
public void run() {
setName("ProcessMonitor");
InputStreamReader br = null;
BufferedReader br = null;
try {
InputStream in = p.getRawProcess().getInputStream();
try {
br = new InputStreamReader(in, System.getProperty("sun.jnu.encoding", "UTF-8"));
br = new BufferedReader(new InputStreamReader(in, System.getProperty("sun.jnu.encoding", "UTF-8")));
} catch (UnsupportedEncodingException ex) {
HMCLog.warn("Unsupported encoding: " + System.getProperty("sun.jnu.encoding", "UTF-8"), ex);
br = new InputStreamReader(in);
br = new BufferedReader(new InputStreamReader(in));
}
int ch;
String line = "";
while (p.isRunning())
while ((ch = br.read()) != -1)
if (ch == '\n') {
printlnEvent.execute(line);
System.out.println("Minecraft: " + line);
p.getStdOutLines().add(line);
line = "";
} else
line += (char) ch;
while ((ch = br.read()) != -1)
if (ch == '\n') {
while ((line = br.readLine()) != null) {
printlnEvent.execute(line);
System.out.println("Minecraft: " + line);
p.getStdOutLines().add(line);
line = "";
} else
line += (char) ch;
}
while ((line = br.readLine()) != null) {
printlnEvent.execute(line);
System.out.println("Minecraft: " + line);
p.getStdOutLines().add(line);
}
stopEvent.execute(p);
} catch (IOException e) {
HMCLog.err("An error occured when reading process stdout/stderr.", e);

View File

@@ -19,6 +19,7 @@ package org.jackhuang.hellominecraft.util.system;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -34,7 +35,7 @@ import org.jackhuang.hellominecraft.util.func.BiFunction;
*
* @author huangyuhui
*/
public class ZipEngine {
public class ZipEngine implements Closeable {
byte[] buf = new byte[1024];
ZipOutputStream zos;
@@ -44,7 +45,8 @@ public class ZipEngine {
zos = new ZipOutputStream(new BufferedOutputStream(os));
}
public void closeFile() throws IOException {
@Override
public void close() throws IOException {
zos.closeEntry();
zos.close();
}

View File

@@ -36,6 +36,7 @@ public class DoubleTask extends TaskInfo {
super(info);
this.a = a;
this.b = b;
hidden = true;
}
@Override

View File

@@ -28,6 +28,10 @@ public class ParallelTask extends Task {
Collection<Task> dependsTask = new HashSet<>();
public ParallelTask() {
hidden = true;
}
@Override
public void executeTask(boolean areDependTasksSucceeded) {
}

View File

@@ -50,6 +50,12 @@ public abstract class Task {
public boolean isAborted() {
return aborted;
}
protected boolean hidden = false;
public boolean isHidden() {
return hidden;
}
public Throwable getFailReason() {
return failReason;

View File

@@ -218,7 +218,7 @@ public class TaskWindow extends javax.swing.JDialog
@Override
public void onDoing(Task task, Collection<Task> taskCollection) {
if (task == null)
if (task == null || task.isHidden())
return;
task.setProgressProviderListener(this);
@@ -238,8 +238,10 @@ public class TaskWindow extends javax.swing.JDialog
@Override
public void onDone(Task task, Collection<Task> taskCollection) {
if (task == null || task.isHidden())
return;
SwingUtilities.invokeLater(() -> {
if (taskList == null || task == null)
if (taskList == null)
return;
pgsTotal.setMaximum(taskList.taskCount());
pgsTotal.setValue(pgsTotal.getValue() + 1);
@@ -254,8 +256,10 @@ public class TaskWindow extends javax.swing.JDialog
@Override
public void onFailed(Task task) {
if (task == null || task.isHidden())
return;
SwingUtilities.invokeLater(() -> {
if (taskList == null || task == null)
if (taskList == null)
return;
String msg = null;
if (task.getFailReason() != null && !(task.getFailReason() instanceof NoShownTaskException))
@@ -283,13 +287,14 @@ public class TaskWindow extends javax.swing.JDialog
@Override
public void setStatus(Task task, String sta) {
if (task == null || task.isHidden())
return;
SwingUtilities.invokeLater(() -> {
if (taskList == null || task == null)
if (taskList == null)
return;
int idx = tasks.indexOf(task);
if (idx == -1)
return;
SwingUtils.setValueAt(lstDownload, task.getInfo() + ": " + sta, idx, 0);
if (idx != -1)
SwingUtils.setValueAt(lstDownload, task.getInfo() + ": " + sta, idx, 0);
});
}

View File

@@ -92,23 +92,10 @@ public class FileDownloadTask extends Task implements PreviousResult<File>, Prev
boolean shouldContinue = true;
private void closeFiles() {
// Close file.
if (file != null)
try {
file.close();
file = null;
} catch (IOException e) {
HMCLog.warn("Failed to close file", e);
}
// Close connection to server.
if (stream != null)
try {
stream.close();
stream = null;
} catch (IOException e) {
HMCLog.warn("Failed to close stream", e);
}
IOUtils.closeQuietly(file);
file = null;
IOUtils.closeQuietly(stream);
stream = null;
}
// Download file.

View File

@@ -187,6 +187,7 @@ message.error=错误
message.cannot_open_explorer=无法打开文件管理器:
message.cancelled=已取消
message.info=提示
message.loading=加载中...
folder.game=游戏文件夹
folder.mod=MOD文件夹

View File

@@ -187,6 +187,7 @@ message.error=\u9519\u8bef
message.cannot_open_explorer=\u65e0\u6cd5\u6253\u5f00\u6587\u4ef6\u7ba1\u7406\u5668:
message.cancelled=\u5df2\u53d6\u6d88
message.info=\u63d0\u793a
message.loading=\u52a0\u8f7d\u4e2d...
folder.game=\u6e38\u620f\u6587\u4ef6\u5939
folder.mod=MOD\u6587\u4ef6\u5939

View File

@@ -187,6 +187,7 @@ message.error=Error
message.cannot_open_explorer=Cannot open explorer:
message.cancelled=Cancelled
message.info=Info
message.loading=Loading...
folder.game=Game Dir
folder.mod=Mod

View File

@@ -187,6 +187,7 @@ message.error=Error
message.cannot_open_explorer=Cannot open explorer:
message.cancelled=Cancelled
message.info=Info
message.loading=Loading...
folder.game=Game Dir
folder.mod=Mod

View File

@@ -187,6 +187,7 @@ message.error=錯誤
message.cannot_open_explorer=無法打開資料管理器:
message.cancelled=已取消
message.info=提示
message.loading=加載中...
folder.game=遊戲資料夾
folder.mod=MOD資料夾

View File

@@ -187,6 +187,7 @@ message.error=\u932f\u8aa4
message.cannot_open_explorer=\u7121\u6cd5\u6253\u958b\u8cc7\u6599\u7ba1\u7406\u5668:
message.cancelled=\u5df2\u53d6\u6d88
message.info=\u63d0\u793a
message.loading=\u52a0\u8f09\u4e2d...
folder.game=\u904a\u6232\u8cc7\u6599\u593e
folder.mod=MOD\u8cc7\u6599\u593e