├── .github
└── ISSUE_TEMPLATE
│ └── bug---.md
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── libs
└── jgit-shrunk.jar
└── src
├── main
├── java
│ └── org
│ │ └── cfpa
│ │ └── i18nupdatemod
│ │ ├── I18nConfig.java
│ │ ├── I18nUpdateMod.java
│ │ ├── I18nUtils.java
│ │ ├── command
│ │ ├── CmdGetLangpack.java
│ │ ├── CmdNotice.java
│ │ ├── CmdReload.java
│ │ ├── CmdReport.java
│ │ ├── CmdToken.java
│ │ ├── CmdUpload.java
│ │ ├── POJOResult.java
│ │ └── package-info.java
│ │ ├── download
│ │ ├── DownloadInfoHelper.java
│ │ ├── DownloadStatus.java
│ │ ├── DownloadWindow.java
│ │ ├── FileDownloadManager.java
│ │ ├── IDownloadManager.java
│ │ └── RepoUpdateManager.java
│ │ ├── git
│ │ └── ResourcePackRepository.java
│ │ ├── hotkey
│ │ └── HotKeyHandler.java
│ │ ├── installer
│ │ └── ResourcePackInstaller.java
│ │ ├── notice
│ │ ├── NoticeGui.java
│ │ ├── NoticeShower.java
│ │ └── ShowNoticeFirst.java
│ │ └── resourcepack
│ │ ├── AssetMap.java
│ │ └── ResourcePackBuilder.java
└── resources
│ ├── LICENSE-JGit
│ ├── META-INF
│ └── i18nmod_at.cfg
│ ├── assets
│ └── i18nmod
│ │ ├── asset_map
│ │ └── asset_map.json
│ │ ├── icon
│ │ └── pack.png
│ │ └── lang
│ │ └── en_us.lang
│ ├── mcmod.info
│ └── pack.mcmeta
└── test
└── java
├── TestDownloader.java
├── TestNotice.java
└── TestUtils.java
/.github/ISSUE_TEMPLATE/bug---.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: bug 反馈
3 | about: 反馈会导致游戏崩溃或资源包无法加载的问题
4 | title: BUG 反馈
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### 描述
11 |
12 |
13 |
14 | ### 日志/崩溃报告
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # eclipse
2 | bin
3 | *.launch
4 | .settings
5 | .metadata
6 | .classpath
7 | .project
8 |
9 | # idea
10 | out
11 | *.ipr
12 | *.iws
13 | *.iml
14 | .idea
15 |
16 | # gradle
17 | build
18 | .gradle
19 |
20 | # other
21 | eclipse
22 | run
23 | test
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 CFPA Org
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 停止维护公告
2 |
3 | 该 mod (1.12.2 版本汉化更新模组)已经停止维护,高版本请移步 [I18nReborn](https://github.com/CFPAOrg/I18nReborn)。
4 |
5 | 本仓库于 2021 年 1 月 22 日归档。
6 |
7 | # 功能简介
8 |
9 | 通过一个基于 Minecraft Forge 的模组,来下载资源包,并在启动前就修改相关配置,从而在游戏启动后自动装上。
10 | 同时添加了更多的实用性功能:一键问题反馈,一键进入 Weblate 模组翻译项目对应位置,游戏内动态公告等功能。
11 | 还添加了很多配置,玩家可在自行修改对应配置,实现部分功能的自定义或者开启关闭。
12 |
13 | ## 1. 自动更新部分
14 |
15 | 能够在启动前下载最新版本资源包:
16 |
17 | - 启动时检查远程云端(使用七牛云)最新版资源包文件。资源包目前大小为 5M 左右,下载速度能达到 1M/s,下载耗时只有四五秒;
18 | - 远程云端文件与 GitHub 对应汉化仓库绑定。通过 Travis 自动集成工具实现远程云端汉化资源包自动更新;
19 | - 下载时会自动弹出进度条。同时设置了时间阈值,超时会自动取消线程阻塞,转为后台下载;
20 | - 下载前会先进行一次大小检查。符合大小就判定为当前文件为最新,同时跳过下载;
21 | - 设置了下载检查时间间隔。玩家可自定义设定 0-30 天的检查间隔;
22 | - 下载后能够自动加载汉化资源包,同时将游戏语言设置为简体中文;
23 |
24 |
25 | 
26 |
27 | ## 2. 游戏内反馈功能
28 |
29 | 能够让玩家在游戏内通过快捷键反馈相关错误或者意见,同时提供编辑的翻译界面快速打开:
30 |
31 | - 支持 JEI,玩家鼠标指针指向的物品,摁下快捷键 K(可修改),可以打开反馈界面;
32 | - 反馈界面目前暂时使用腾讯问卷系统,玩家只需要粘贴对应信息,同时填写简单的问题说明即可提交;
33 | - 游戏内支持快速打开 Weblate 对应翻译功能。玩家对着物品摁下快捷键 L,即可快速打开 Weblate 对应翻译词条(需要你拥有 Weblate 账户,同时 Weblate 上面有此模组翻译文件);
34 | - 玩家手持物品输入 `/lang_report` 也可打开反馈界面;
35 |
36 | 
37 |
38 | ## 3. 公告功能
39 |
40 | 能够让玩家在游戏内打开公告,公告获取指定动态服务器内容,发布相关更新消息,或者其他说明:
41 |
42 | - 公告在玩家首次进入存档或者服务器会显示一次;
43 | - 公告是动态的,通过联网来获取数据;
44 | - 可以通过配置文件关闭这个功能,或者自定义按钮,自定义远程公告链接;
45 | - 游戏内输入 `/lang_notice` 命令即可再次打开公告;
46 |
47 | 
48 |
49 |
50 |
51 | ## 4. 自定义配置
52 |
53 | 提供了多个配置可供调节,专为整合作者,服务器服主提供。可自定义的配置有如下条目,均需要重启游戏:
54 |
55 | 
56 |
57 | 资源包下载模块:
58 | - 更新检查间隔(0-30 天可自定义)
59 | - 资源包链接(整合作者,服主可自定义资源包下载源)
60 | - 资源包名称(整合作者,服主可自定义资源包下载后的名称)
61 | - 下载窗口名称
62 | - 下载阈值(超过此阈值会自动转为后台下载)
63 | - 是否开启强制切换中文功能
64 |
65 | 公告显示模块:
66 | - 是否强制显示公告
67 | - 公告源链接(整合作者、服主可自定义自己的公告链接)
68 | - 是否显示参与汉化的按钮
69 |
70 | 问题反馈模块:
71 | - 自定义打开的问题反馈界面(整合作者、服主可自定义自己的问题反馈链接)
72 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | maven { url = "http://files.minecraftforge.net/maven" }
5 | }
6 | dependencies {
7 | classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT'
8 | classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.4'
9 | }
10 | }
11 |
12 | apply plugin: 'idea'
13 | apply plugin: 'net.minecraftforge.gradle.forge'
14 | //Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
15 |
16 | apply plugin: 'com.github.johnrengelman.shadow'
17 |
18 | repositories {
19 | maven {
20 | name = "jei"
21 | url = "http://dvs1.progwml6.com/files/maven"
22 | }
23 | }
24 |
25 | version = modVersion
26 | group = "org.cfpa.i18nupdatemod" // http://maven.apache.org/guides/mini/guide-naming-conventions.html
27 | archivesBaseName = "i18nupdatemod"
28 |
29 | sourceCompatibility = targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
30 | compileJava {
31 | sourceCompatibility = targetCompatibility = '1.8'
32 | }
33 |
34 | minecraft {
35 | version = "1.12.2-14.23.4.2738"
36 | runDir = "run"
37 |
38 | // the mappings can be changed at any time, and must be in the following format.
39 | // snapshot_YYYYMMDD snapshot are built nightly.
40 | // stable_# stables are built at the discretion of the MCP team.
41 | // Use non-default mappings at your own risk. they may not always work.
42 | // simply re-run your setup task after changing the mappings to update your workspace.
43 | mappings = "snapshot_20180701"
44 | // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
45 | replace "@VERSION@", project.version
46 | }
47 |
48 | configurations {
49 | compile.extendsFrom shadow
50 | }
51 |
52 | dependencies {
53 | // you may put jars on which you depend on in ./libs
54 | // or you may define them like so..
55 | //compile "some.group:artifact:version:classifier"
56 | //compile "some.group:artifact:version"
57 |
58 | // real examples
59 | //compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
60 | //compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env
61 |
62 | // the 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime.
63 | //provided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
64 |
65 | // the deobf configurations: 'deobfCompile' and 'deobfProvided' are the same as the normal compile and provided,
66 | // except that these dependencies get remapped to your current MCP mappings
67 | //deobfCompile 'com.mod-buildcraft:buildcraft:6.0.8:dev'
68 | //deobfProvided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
69 |
70 | // for more info...
71 | // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
72 | // http://www.gradle.org/docs/current/userguide/dependency_management.html
73 | deobfCompile "mezz.jei:jei_${minecraft.version}:4.14.3.243"
74 |
75 | // https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime
76 | shadow 'org.apache.httpcomponents:httpmime:4.3.3'
77 |
78 | testCompile "junit:junit:4.12"
79 | shadow fileTree(dir: 'libs', include: ['*.jar'])
80 | shadow 'org.slf4j:slf4j-api:1.7.5'
81 | shadow 'org.slf4j:slf4j-simple:1.7.21'
82 | }
83 |
84 | shadowJar {
85 | configurations = [project.configurations.shadow]
86 |
87 | relocate "org.apache.http.entity.mime", "org.cfpa.i18nupdatemod.repack.org.apache.http.entity.mime"
88 | relocate "org.slf4j", "org.cfpa.i18nupdatemod.repack.org.slf4j"
89 | classifier = null
90 | dependencies {
91 | exclude(dependency('org.apache.httpcomponents:httpclient:4.3.3'))
92 | exclude(dependency('org.apache.httpcomponents:httpcore:4.3.2'))
93 | exclude(dependency('commons-codec:commons-codec:1.6'))
94 | exclude(dependency('commons-logging:commons-logging:1.2'))
95 | }
96 | }
97 | reobf {
98 | shadowJar { mappingType = 'SEARGE' }
99 | }
100 | tasks.reobfShadowJar.mustRunAfter shadowJar
101 |
102 | processResources {
103 | // this will ensure that this task is redone when the versions change.
104 | inputs.property "version", project.version
105 | inputs.property "mcversion", project.minecraft.version
106 |
107 | // replace stuff in mcmod.info, nothing else
108 | from(sourceSets.main.resources.srcDirs) {
109 | include 'mcmod.info'
110 |
111 | // replace version and mcversion
112 | expand 'version': project.version, 'mcversion': project.minecraft.version
113 | }
114 |
115 | // copy everything else except the mcmod.info
116 | from(sourceSets.main.resources.srcDirs) {
117 | exclude 'mcmod.info'
118 | }
119 | }
120 |
121 | tasks.withType(JavaCompile) {
122 | options.encoding = 'UTF-8'
123 | }
124 |
125 | jar {
126 | manifest {
127 | attributes 'FMLAT': 'i18nmod_at.cfg'
128 | // attributes 'ContainedDeps': 'httpmime-4.3.3.jar'
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Sets default memory used for gradle commands. Can be overridden by user or command line properties.
2 | # This is required to provide enough memory for the Minecraft decompilation process.
3 | org.gradle.jvmargs=-Xmx4G
4 | modVersion=1.12.2-2.0.0-alpha
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CFPAOrg/I18nUpdateMod/47bfc71b816e2b370bb9ac338a06b863b91c75da/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Sep 14 12:28:28 PDT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/libs/jgit-shrunk.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CFPAOrg/I18nUpdateMod/47bfc71b816e2b370bb9ac338a06b863b91c75da/libs/jgit-shrunk.jar
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/I18nConfig.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod;
2 |
3 | import net.minecraftforge.common.config.Config;
4 | import net.minecraftforge.common.config.ConfigManager;
5 | import net.minecraftforge.fml.client.event.ConfigChangedEvent;
6 | import net.minecraftforge.fml.common.Mod;
7 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
8 |
9 | @Config(modid = I18nUpdateMod.MODID, name = "i18n_update_mod", category = "i18n_mod")
10 | public class I18nConfig {
11 | @Config.Name("公告配置")
12 | public static Notice notice = new Notice();
13 | @Config.Name("资源包下载配置")
14 | public static Download download = new Download();
15 | @Config.Name("按键配置")
16 | public static Key key = new Key();
17 | @Config.Name("启用国际化配置")
18 | public static Internationalization internationalization = new Internationalization();
19 | @Config.Name("优先加载资源包")
20 | @Config.Comment("是否将资源包设为最高优先级")
21 | @Config.RequiresMcRestart
22 | public static boolean priority = true;
23 |
24 | public static class Notice {
25 | @Config.Name("是否显示通知")
26 | @Config.Comment("默认玩家每次重启游戏会加载一次公告,可以通过该配置禁用")
27 | @Config.RequiresMcRestart
28 | public boolean showNoticeConfig = true;
29 |
30 | @Config.Name("是否显示参与汉化按钮")
31 | @Config.Comment("默认首次显示的公告左侧会有参与汉化的按钮,可以通过配置禁用")
32 | @Config.RequiresMcRestart
33 | public boolean showWeblateButton = true;
34 |
35 | @Config.Name("公告链接")
36 | @Config.Comment("专为整合作者设计,你只需要提供一个纯网页版txt文件(必须是UTF-8格式编码!)链接,即可加载此公告")
37 | public String noticeURL = "http://downloader.meitangdehulu.com/Notice.txt";
38 | }
39 |
40 | public static class Download {
41 | @Config.Name("更新检测间隔(天)")
42 | @Config.RequiresMcRestart
43 | @Config.Comment("通过修改此处设定更新检测间隔,单位为天。设置为0表示每次启动游戏都检测")
44 | @Config.RangeInt(min = 0, max = 30)
45 | public int maxDay = 3;
46 |
47 | @Config.Name("本地资源包仓库路径")
48 | @Config.Comment(
49 | "默认 Auto 会根据系统自动选择路径\n" +
50 | "Windows 的默认路径为 USER_HOME/AppData/I18nUpdateMod 。\n" +
51 | "Linux/macOS 等类 Unix 系统默认路径为 USER_HOME/.I18nUpdateMod 。"
52 | )
53 | @Config.RequiresMcRestart
54 | public String localRepoPath = "Auto";
55 |
56 | @Config.Name("远程资源包仓库地址列表")
57 | @Config.Comment("按列表中的顺序尝试从远程仓库获取更新")
58 | @Config.RequiresMcRestart
59 | public String[] remoteRepoURL = {
60 | "https://git.dev.tencent.com/baka943/Minecraft-Mod-Language-Package.git",
61 | "https://git.cfpa.team/main.html",
62 | "https://github.com/CFPAOrg/Minecraft-Mod-Language-Package.git",
63 | "https://git.cfpa.team/fallback.html"
64 | };
65 |
66 | @Config.Name("生成的资源包名称")
67 | @Config.Comment("用来自定义模组生成的资源包名称")
68 | @Config.RequiresMcRestart
69 | public String i18nLangPackName = "I18n-Mod-Language-Pack";
70 |
71 | @Config.Name("下载条名称")
72 | @Config.Comment("用来自定义下载过程中小窗口的名字")
73 | @Config.RequiresMcRestart
74 | public String dlWindowsName = "汉化资源包更新进度条";
75 |
76 | @Config.Name("超时时间(秒)")
77 | @Config.RequiresMcRestart
78 | @Config.Comment("超过多少时间,取消主线程阻塞,转为后台下载")
79 | @Config.RangeInt(min = 1)
80 | public int maxTime = 60;
81 |
82 | @Config.Name("是否开启强制中文功能")
83 | @Config.RequiresMcRestart
84 | @Config.Comment("默认开启,会在启动时将游戏语言强制设定为中文")
85 | public boolean setupChinese = true;
86 |
87 | @Config.Name("是否开启资源包下载功能")
88 | @Config.RequiresMcRestart
89 | @Config.Comment("默认开启,关闭后此模组不再尝试更新本地仓库和安装资源包")
90 | public boolean shouldDownload = true;
91 | }
92 |
93 | public static class Key {
94 | @Config.Name("自定义反馈按键打开网址")
95 | @Config.Comment("可能会有人想自定义")
96 | @Config.RequiresMcRestart
97 | public String reportURL = "http://issues.cfpa.team";
98 |
99 | @Config.Name("是否关闭所有键位")
100 | @Config.Comment("为腐竹设计,防止玩家乱改按键导致问题")
101 | @Config.RequiresMcRestart
102 | public Boolean closedKey = false;
103 | }
104 |
105 | public static class Internationalization {
106 | @Config.Name("启用国际化")
107 | @Config.Comment("启用后,将依据系统语言来选择开启或关闭资源包下载、公告显示、键位指令注册")
108 | @Config.RequiresMcRestart
109 | public boolean openI18n = false;
110 | }
111 |
112 | // 用于 GUI 界面配置调节的保存
113 | @Mod.EventBusSubscriber(modid = I18nUpdateMod.MODID)
114 | public static class ConfigSyncHandler {
115 | @SubscribeEvent
116 | public static void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) {
117 | if (event.getModID().equals(I18nUpdateMod.MODID)) {
118 | ConfigManager.sync(I18nUpdateMod.MODID, Config.Type.INSTANCE);
119 | I18nUpdateMod.logger.info("配置文件修改已经保存");
120 | }
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/I18nUpdateMod.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod;
2 |
3 | import net.minecraftforge.client.ClientCommandHandler;
4 | import net.minecraftforge.common.MinecraftForge;
5 | import net.minecraftforge.fml.common.Mod;
6 | import net.minecraftforge.fml.common.event.FMLConstructionEvent;
7 | import net.minecraftforge.fml.common.event.FMLInitializationEvent;
8 | import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
9 | import org.apache.logging.log4j.LogManager;
10 | import org.apache.logging.log4j.Logger;
11 | import org.cfpa.i18nupdatemod.command.*;
12 | import org.cfpa.i18nupdatemod.download.DownloadInfoHelper;
13 | import org.cfpa.i18nupdatemod.download.DownloadStatus;
14 | import org.cfpa.i18nupdatemod.download.RepoUpdateManager;
15 | import org.cfpa.i18nupdatemod.git.ResourcePackRepository;
16 | import org.cfpa.i18nupdatemod.hotkey.HotKeyHandler;
17 | import org.cfpa.i18nupdatemod.installer.ResourcePackInstaller;
18 | import org.cfpa.i18nupdatemod.notice.ShowNoticeFirst;
19 | import org.cfpa.i18nupdatemod.resourcepack.ResourcePackBuilder;
20 |
21 | import java.io.File;
22 |
23 | import static org.cfpa.i18nupdatemod.I18nUtils.isChinese;
24 | import static org.cfpa.i18nupdatemod.I18nUtils.setupLang;
25 |
26 | @Mod(modid = I18nUpdateMod.MODID,
27 | name = I18nUpdateMod.NAME,
28 | clientSideOnly = true,
29 | acceptedMinecraftVersions = "[1.12]",
30 | version = I18nUpdateMod.VERSION,
31 | dependencies = "after:defaultoptions")
32 | public class I18nUpdateMod {
33 | public final static String MODID = "i18nmod";
34 | public final static String NAME = "I18n Update Mod";
35 | public final static String VERSION = "@VERSION@";
36 |
37 | public static final Logger logger = LogManager.getLogger(MODID);
38 |
39 | private boolean shouldDisplayErrorScreen = false;
40 |
41 | @Mod.EventHandler
42 | public void construct(FMLConstructionEvent event) {
43 | // 国际化检查
44 | if (I18nConfig.internationalization.openI18n && !isChinese()) {
45 | return;
46 | }
47 |
48 | DownloadInfoHelper.init();
49 |
50 | // 设置中文
51 | if (I18nConfig.download.setupChinese) {
52 | setupLang();
53 | }
54 |
55 | if (!I18nConfig.download.shouldDownload) {
56 | return;
57 | }
58 |
59 | ResourcePackBuilder builder = new ResourcePackBuilder();
60 | boolean needUpdate = builder.checkUpdate();
61 | ResourcePackInstaller.setResourcesRepository();
62 |
63 | if (needUpdate) {
64 | String localPath;
65 | try {
66 | localPath = new File(I18nUtils.getLocalRepositoryFolder(I18nConfig.download.localRepoPath), "I18nRepo").getPath();
67 | } catch (IllegalArgumentException e) {
68 | shouldDisplayErrorScreen = true;
69 | return;
70 | }
71 | ResourcePackRepository repo = new ResourcePackRepository(localPath, builder.getAssetDomains());
72 | RepoUpdateManager updateManager = new RepoUpdateManager(repo);
73 | updateManager.update();
74 | if (updateManager.getStatus() == DownloadStatus.SUCCESS) {
75 | builder.updateAllNeededFilesFromRepo(repo);
76 | builder.touch();
77 | ShowNoticeFirst.shouldShowNotice = true;
78 | }
79 | }
80 | }
81 |
82 | @Mod.EventHandler
83 | public void preInit(FMLPreInitializationEvent event) {
84 | if (shouldDisplayErrorScreen) {
85 | throw new I18nUtils.InvalidPathConfigurationException();
86 | }
87 | }
88 |
89 | @Mod.EventHandler
90 | public void init(FMLInitializationEvent event) {
91 | // 国际化检查
92 | if (I18nConfig.internationalization.openI18n && !isChinese()) {
93 | return;
94 | }
95 |
96 | // 命令注册
97 | ClientCommandHandler.instance.registerCommand(new CmdNotice());
98 | ClientCommandHandler.instance.registerCommand(new CmdReport());
99 | ClientCommandHandler.instance.registerCommand(new CmdReload());
100 | ClientCommandHandler.instance.registerCommand(new CmdGetLangpack());
101 | ClientCommandHandler.instance.registerCommand(new CmdUpload());
102 | ClientCommandHandler.instance.registerCommand(new CmdToken());
103 |
104 | // 热键注册
105 | if (!I18nConfig.key.closedKey) {
106 | MinecraftForge.EVENT_BUS.register(new HotKeyHandler());
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/I18nUtils.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.nio.file.FileVisitResult;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.nio.file.SimpleFileVisitor;
9 | import java.nio.file.StandardCopyOption;
10 | import java.nio.file.attribute.BasicFileAttributes;
11 | import java.util.HashMap;
12 | import java.util.List;
13 | import java.util.Map;
14 |
15 | import javax.annotation.Nonnull;
16 | import javax.annotation.Nullable;
17 |
18 | import net.minecraft.client.gui.FontRenderer;
19 | import net.minecraft.client.gui.GuiErrorScreen;
20 | import net.minecraftforge.fml.client.CustomModLoadingErrorDisplayException;
21 | import net.minecraftforge.fml.common.ICrashCallable;
22 | import org.apache.commons.io.FileUtils;
23 |
24 | import com.google.common.base.Splitter;
25 | import com.google.common.collect.Iterables;
26 |
27 | import net.minecraft.client.Minecraft;
28 | import net.minecraft.client.settings.GameSettings;
29 |
30 | public class I18nUtils {
31 | public I18nUtils() {
32 | throw new UnsupportedOperationException("no instance");
33 | }
34 |
35 | /**
36 | * 将语言换成中文
37 | */
38 | static void setupLang() {
39 | Minecraft mc = Minecraft.getMinecraft();
40 | GameSettings gameSettings = mc.gameSettings;
41 | // 强行修改为简体中文
42 | if (!gameSettings.language.equals("zh_cn")) {
43 | mc.getLanguageManager().currentLanguage = "zh_cn";
44 | gameSettings.language = "zh_cn";
45 | }
46 | }
47 |
48 | /**
49 | * 检测系统语言
50 | *
51 | * @return 是否为简体中文语言
52 | */
53 | public static boolean isChinese() {
54 | return System.getProperty("user.language").equals("zh");
55 | }
56 |
57 | /**
58 | * 依据等号切分字符串,将 list 处理成 Map
59 | *
60 | * @param listIn 想要处理的字符串 list
61 | * @return 处理好的 Map
62 | */
63 | public static Map listToMap(List listIn) {
64 | HashMap mapOut = new HashMap<>();
65 |
66 | // 抄袭原版加载方式
67 | Splitter I18N_SPLITTER = Splitter.on('=').limit(2);
68 |
69 | // 遍历拆分
70 | for (String s : listIn) {
71 | if (!s.isEmpty() && s.charAt(0) != '#') {
72 | String[] splitString = Iterables.toArray(I18N_SPLITTER.split(s), String.class);
73 |
74 | if (splitString != null && splitString.length == 2) {
75 | String s1 = splitString[0];
76 | String s2 = splitString[1];
77 | mapOut.put(s1, s2);
78 | }
79 | }
80 | }
81 | return mapOut;
82 | }
83 |
84 | /**
85 | * 从文件中获取 Token
86 | *
87 | * @return 得到的 Token
88 | */
89 | @Nullable
90 | public static String readToken() {
91 | File tokenFile = new File(Minecraft.getMinecraft().mcDataDir.toString() + File.separator + "config"
92 | + File.separator + "TOKEN.txt");
93 | try {
94 | List token = FileUtils.readLines(tokenFile, "UTF-8");
95 | if (token.size() != 0) {
96 | return token.get(0);
97 | }
98 | } catch (IOException ioe) {
99 | ioe.printStackTrace();
100 | }
101 | return null;
102 | }
103 |
104 | static String getLocalRepositoryFolder(String path) throws IllegalArgumentException {
105 | String OS = System.getProperty("os.name").toLowerCase();
106 | String folder;
107 | if (path.equals("Auto")) {
108 | if (OS.contains("mac") || OS.contains("nix") || OS.contains("nux") || OS.contains("aix")) {
109 | String userHome = System.getProperty("user.home");
110 | if (userHome != null) {
111 | folder = new File(userHome, ".I18nUpdateMod").getPath();
112 | } else {
113 | throw new IllegalArgumentException("User home is null.");
114 | }
115 | } else if (OS.contains("win")) {
116 | String appData = System.getenv("APPDATA");
117 | if (appData != null) {
118 | folder = new File(appData, "I18nUpdateMod").getPath();
119 | } else {
120 | throw new IllegalArgumentException("AppData path is null.");
121 | }
122 | } else {
123 | throw new IllegalArgumentException("Can't find out what path should be used.");
124 | }
125 | } else {
126 | File f = new File(path);
127 | if (f.isAbsolute()) {
128 | folder = f.getPath();
129 | } else {
130 | folder = new File(Minecraft.getMinecraft().mcDataDir, f.getPath()).getPath();
131 | }
132 | }
133 | return folder;
134 | }
135 |
136 | public static void copyDir(Path sourceDir, Path targetDir) throws IOException {
137 | Files.walkFileTree(sourceDir, new CopyDir(sourceDir, targetDir));
138 | }
139 |
140 | static class CopyDir extends SimpleFileVisitor {
141 | private Path sourceDir;
142 | private Path targetDir;
143 |
144 | CopyDir(Path sourceDir, Path targetDir) {
145 | this.sourceDir = sourceDir;
146 | this.targetDir = targetDir;
147 | }
148 |
149 | @Override
150 | public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
151 | Path targetFile = targetDir.resolve(sourceDir.relativize(file));
152 | Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING);
153 | return FileVisitResult.CONTINUE;
154 | }
155 |
156 | @Override
157 | public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attributes) throws IOException {
158 | Path newDir = targetDir.resolve(sourceDir.relativize(dir));
159 | if (!newDir.toFile().exists()) {
160 | Files.createDirectory(newDir);
161 | }
162 | return FileVisitResult.CONTINUE;
163 | }
164 | }
165 |
166 | static class InvalidPathConfigurationException extends CustomModLoadingErrorDisplayException {
167 |
168 | public InvalidPathConfigurationException() {
169 | super("InvalidConfiguration", null);
170 | }
171 |
172 | @Override
173 | public void initGui(GuiErrorScreen errorScreen, FontRenderer fontRenderer) {
174 | }
175 |
176 | @Override
177 | public void drawScreen(GuiErrorScreen errorScreen, FontRenderer fontRenderer, int mouseRelX, int mouseRelY, float tickTime) {
178 | final List text = fontRenderer.listFormattedStringToWidth("本地仓库路径无效,请更改配置文件中的本地资源包仓库地址。", errorScreen.width - 80);
179 | int yOffset = 50;
180 | for (String sentence : text) {
181 | errorScreen.drawCenteredString(fontRenderer, sentence, errorScreen.width / 2, yOffset, 0xFFFFFF);
182 | yOffset += 10;
183 | }
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/command/CmdGetLangpack.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.command;
2 |
3 | import net.minecraft.client.Minecraft;
4 | import net.minecraft.command.CommandBase;
5 | import net.minecraft.command.ICommandSender;
6 | import net.minecraft.server.MinecraftServer;
7 | import net.minecraft.util.math.BlockPos;
8 | import net.minecraft.util.text.TextComponentTranslation;
9 | import org.apache.commons.io.FileUtils;
10 | import org.cfpa.i18nupdatemod.I18nUtils;
11 | import org.cfpa.i18nupdatemod.download.DownloadStatus;
12 | import org.cfpa.i18nupdatemod.download.FileDownloadManager;
13 |
14 | import javax.annotation.Nullable;
15 | import java.io.File;
16 | import java.io.IOException;
17 | import java.nio.charset.StandardCharsets;
18 | import java.util.ArrayList;
19 | import java.util.List;
20 | import java.util.Map;
21 | import java.util.regex.Matcher;
22 |
23 | public class CmdGetLangpack extends CommandBase {
24 | @Override
25 | public String getName() {
26 | return "lang_get";
27 | }
28 |
29 | @Override
30 | public String getUsage(ICommandSender sender) {
31 | return "快速重载语言文件,用于测试汉化";
32 | }
33 |
34 | @Override
35 | public int getRequiredPermissionLevel() {
36 | return 0;
37 | }
38 |
39 | @Override
40 | public void execute(MinecraftServer server, ICommandSender sender, String[] args) {
41 | // 参数为空,警告
42 | if (args.length == 0) {
43 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_get_langpack.empty"));
44 | return;
45 | }
46 |
47 | // 参数存在,进行下一步判定
48 | if (Minecraft.getMinecraft().getResourceManager().getResourceDomains().contains(args[0])) {
49 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_get_langpack.right_start", args[0]));
50 |
51 | // 同名资源包存在,直接返回
52 | if (!cerateTempLangpack(args[0])) {
53 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_get_langpack.error_create_folder"));
54 | return;
55 | }
56 |
57 | // 主下载功能
58 | langFileDownloader(args[0]);
59 | }
60 | // 参数不存在,警告
61 | else {
62 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_get_langpack.not_found", args[0]));
63 | }
64 | }
65 |
66 | @Override
67 | public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos) {
68 | // 如果输入参数为空,返回整个列表
69 | if (args.length == 0) {
70 | return new ArrayList<>(Minecraft.getMinecraft().getResourceManager().getResourceDomains());
71 | }
72 |
73 | // 如果输入不为空,从头字符串检索,进行输出
74 | List availableArgs = new ArrayList<>();
75 | for (String modid : Minecraft.getMinecraft().getResourceManager().getResourceDomains()) {
76 | if (modid.indexOf(args[0]) == 0) {
77 | availableArgs.add(modid);
78 | }
79 | }
80 | return availableArgs;
81 | }
82 |
83 | /**
84 | * 构建资源包文件夹
85 | *
86 | * @param modid 想要下载的模组资源 id
87 | * @return 是否构建成功
88 | */
89 | private boolean cerateTempLangpack(String modid) {
90 | // 构建文件夹
91 | File tempDir = new File(String.format(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator + "%s_tmp_resource_pack" + File.separator + "assets" + File.separator + "%s" + File.separator + "lang", modid, modid));
92 | if (tempDir.exists() || !tempDir.mkdirs()) {
93 | return false;
94 | }
95 |
96 | // 构建 pack.mcmeta
97 | File tempPackFile = new File(String.format(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator + "%s_tmp_resource_pack" + File.separator + "pack.mcmeta", modid));
98 | String metaText = String.format("{\"pack\":{\"pack_format\":3,\"description\":\"临时汉化资源包,仅包含 %s 模组中英文文件\"}}", modid);
99 |
100 | // 判定文件是否存在
101 | if (tempPackFile.exists()) {
102 | return false;
103 | }
104 |
105 | // 写入数据
106 | try {
107 | FileUtils.write(tempPackFile, metaText, StandardCharsets.UTF_8);
108 | } catch (IOException ioe) {
109 | return false;
110 | }
111 |
112 | // 走到了这一步,恭喜你
113 | return true;
114 | }
115 |
116 | /**
117 | * 处理中英文文件,弄成混编,方便玩家翻译
118 | *
119 | * @param modid 想要下载的模组资源 id
120 | * @return 是否处理成功
121 | */
122 | private boolean handleLangpack(String modid) {
123 | try {
124 | // 临时文件
125 | List tmpFile = new ArrayList<>();
126 |
127 | // 读取中英文文件
128 | List en_us = FileUtils.readLines(new File(String.format(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator + "%s_tmp_resource_pack" + File.separator + "assets" + File.separator + "%s" + File.separator + "lang" + File.separator + "en_us.lang", modid, modid)), StandardCharsets.UTF_8);
129 | List zh_cn = FileUtils.readLines(new File(String.format(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator + "%s_tmp_resource_pack" + File.separator + "assets" + File.separator + "%s" + File.separator + "lang" + File.separator + "zh_cn.lang", modid, modid)), StandardCharsets.UTF_8);
130 |
131 | // 处理成 HashMap
132 | Map chineseMap = I18nUtils.listToMap(zh_cn);
133 |
134 | // 接下来,替换
135 | for (String s : en_us) {
136 | // 临时变量,记录是否已经存在汉化
137 | boolean isExist = false;
138 |
139 | // 遍历查找
140 | for (String key : chineseMap.keySet()) {
141 | // 存在!
142 | if (s.indexOf(key + '=') == 0) {
143 | // 替换,写入临时变量。记住替换字符串需要转义,防止发生 Illegal group reference 错误
144 | tmpFile.add(s.replaceAll("=.*$", "=" + Matcher.quoteReplacement(chineseMap.get(key))));
145 | // 别忘记标记存在
146 | isExist = true;
147 | break;
148 | }
149 | }
150 | // 只有不存在时,才添加源字符串
151 | if (!isExist) {
152 | tmpFile.add(s);
153 | }
154 | }
155 |
156 | // 写入文件
157 | FileUtils.writeLines(new File(String.format(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator + "%s_tmp_resource_pack" + File.separator + "assets" + File.separator + "%s" + File.separator + "lang" + File.separator + "zh_cn.lang", modid, modid)), "UTF-8", tmpFile, "\n", false);
158 |
159 | return true;
160 | } catch (IOException ioe) {
161 | return false;
162 | }
163 | }
164 |
165 | /**
166 | * 下载主程序
167 | *
168 | * @param modid 想要下载的模组资源 id
169 | */
170 | private void langFileDownloader(String modid) {
171 | // 构建单独的下载线程,下载中英文
172 | FileDownloadManager langpackChinese = new FileDownloadManager(String.format("https://coding.net/u/baka943/p/Minecraft-Mod-Language-Package/git/raw/1.12.2/project/assets/%s/lang/zh_cn.lang", modid), "zh_cn.lang", String.format(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator + "%s_tmp_resource_pack" + File.separator + "assets" + File.separator + "%s" + File.separator + "lang", modid, modid));
173 | FileDownloadManager langpackEnglish = new FileDownloadManager(String.format("https://coding.net/u/baka943/p/Minecraft-Mod-Language-Package/git/raw/1.12.2/project/assets/%s/lang/en_us.lang", modid), "en_us.lang", String.format(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator + "%s_tmp_resource_pack" + File.separator + "assets" + File.separator + "%s" + File.separator + "lang", modid, modid));
174 |
175 | // 线程启用
176 | langpackChinese.start("I18n-Download-Chinese-Thread");
177 | langpackEnglish.start("I18n-Download-English-Thread");
178 |
179 | // 开始下载
180 | new Thread(() -> {
181 | // 计时器,用来记录超时时间
182 | int timeRecord = 0;
183 |
184 | // 主循环,除非出错,否则一直定时检测
185 | while (true) {
186 | try {
187 | // 检测间隔,5秒
188 | Thread.sleep(5000);
189 |
190 | // 还在下载?计时器计数,同时提醒游戏内玩家
191 | if (langpackChinese.getStatus() == DownloadStatus.DOWNLOADING || langpackEnglish.getStatus() == DownloadStatus.DOWNLOADING) {
192 | timeRecord = timeRecord + 5;
193 | }
194 |
195 | // 两者都成功?
196 | else if (langpackChinese.getStatus() == DownloadStatus.SUCCESS && langpackEnglish.getStatus() == DownloadStatus.SUCCESS) {
197 | // 处理语言文件,构建成资源包
198 | if (handleLangpack(modid)) {
199 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_get_langpack.right_stop"));
200 | }
201 | // 处理失败,告诉玩家
202 | else {
203 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_get_langpack.error_handle"));
204 | }
205 | // 失败还是成功过,都跳出循环
206 | break;
207 | }
208 | // 其他情况,取消下载
209 | else {
210 | langpackChinese.cancel();
211 | langpackEnglish.cancel();
212 |
213 | // 既然取消下载,那么就删除这个没用的资源包吧
214 | try {
215 | FileUtils.deleteDirectory(new File(String.format(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator + "%s_tmp_resource_pack", modid)));
216 | } catch (IOException ioe) {
217 | ioe.printStackTrace();
218 | }
219 |
220 | // 同时提醒玩家,跳出循环
221 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_get_langpack.error_stop"));
222 | break;
223 | }
224 | } catch (InterruptedException ignore) {
225 | }
226 |
227 | // 下载中,定时提示
228 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_get_langpack.right_downloading", timeRecord));
229 |
230 | // 超时取消下载
231 | if (timeRecord >= 60) {
232 | langpackChinese.cancel();
233 | langpackEnglish.cancel();
234 |
235 | // 既然取消下载,那么就删除这个没用的资源包吧
236 | try {
237 | FileUtils.deleteDirectory(new File(String.format(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator + "%s_tmp_resource_pack", modid)));
238 | } catch (IOException ioe) {
239 | ioe.printStackTrace();
240 | }
241 |
242 | // 同时提醒玩家
243 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_get_langpack.error_timeout"));
244 | return;
245 | }
246 | }
247 | }, "I18n_LANGPACK_THREAD").start();
248 | }
249 | }
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/command/CmdNotice.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.command;
2 |
3 | import net.minecraft.command.CommandBase;
4 | import net.minecraft.command.ICommandSender;
5 | import net.minecraft.server.MinecraftServer;
6 | import org.cfpa.i18nupdatemod.notice.NoticeShower;
7 |
8 | import javax.annotation.Nullable;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | public class CmdNotice extends CommandBase {
13 | public static final List tabChoose = new ArrayList<>();
14 |
15 | @Override
16 | public String getName() {
17 | return "lang_notice";
18 | }
19 |
20 | @Override
21 | @Nullable
22 | public String getUsage(ICommandSender sender) {
23 | return null;
24 | }
25 |
26 | @Override
27 | public void execute(MinecraftServer server, ICommandSender sender, String[] args) {
28 | new NoticeShower();
29 | }
30 |
31 | @Override
32 | public int getRequiredPermissionLevel() {
33 | return 0;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/command/CmdReload.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.command;
2 |
3 | import net.minecraft.client.Minecraft;
4 | import net.minecraft.command.CommandBase;
5 | import net.minecraft.command.ICommandSender;
6 | import net.minecraft.server.MinecraftServer;
7 | import net.minecraft.util.text.TextComponentTranslation;
8 |
9 | public class CmdReload extends CommandBase {
10 | @Override
11 | public String getName() {
12 | return "lang_reload";
13 | }
14 |
15 | @Override
16 | public String getUsage(ICommandSender sender) {
17 | return "快速重载语言文件,用于测试汉化";
18 | }
19 |
20 | @Override
21 | public int getRequiredPermissionLevel() {
22 | return 0;
23 | }
24 |
25 | @Override
26 | public void execute(MinecraftServer server, ICommandSender sender, String[] args) {
27 | Minecraft.getMinecraft().getLanguageManager().onResourceManagerReload(Minecraft.getMinecraft().getResourceManager());
28 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_reload.success"));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/command/CmdReport.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.command;
2 |
3 | import net.minecraft.client.Minecraft;
4 | import net.minecraft.client.gui.GuiScreen;
5 | import net.minecraft.command.CommandBase;
6 | import net.minecraft.command.ICommandSender;
7 | import net.minecraft.item.ItemStack;
8 | import net.minecraft.server.MinecraftServer;
9 | import net.minecraft.util.text.TextComponentTranslation;
10 | import org.cfpa.i18nupdatemod.I18nConfig;
11 |
12 | import javax.annotation.Nullable;
13 | import java.awt.*;
14 | import java.net.URI;
15 |
16 | public class CmdReport extends CommandBase {
17 | @Override
18 | public String getName() {
19 | return "lang_report";
20 | }
21 |
22 | @Override
23 | @Nullable
24 | public String getUsage(ICommandSender sender) {
25 | return null;
26 | }
27 |
28 | @Override
29 | public int getRequiredPermissionLevel() {
30 | return 0;
31 | }
32 |
33 | @Override
34 | public void execute(MinecraftServer server, ICommandSender sender, String[] args) {
35 | ItemStack stack = Minecraft.getMinecraft().player.inventory.getCurrentItem();
36 | if (!stack.isEmpty()) {
37 | String text = String.format("模组ID:%s\n非本地化名称:%s\n显示名称:%s", stack.getItem().getCreatorModId(stack), stack.getItem().getUnlocalizedName(), stack.getDisplayName());
38 | String url = I18nConfig.key.reportURL;
39 | try {
40 | GuiScreen.setClipboardString(text);
41 | Desktop.getDesktop().browse(new URI(url));
42 | } catch (Exception urlException) {
43 | urlException.printStackTrace();
44 | }
45 | } else {
46 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_report.empty"));
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/command/CmdToken.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.command;
2 |
3 | import net.minecraft.client.Minecraft;
4 | import net.minecraft.command.CommandBase;
5 | import net.minecraft.command.ICommandSender;
6 | import net.minecraft.server.MinecraftServer;
7 | import net.minecraft.util.text.TextComponentTranslation;
8 | import org.apache.commons.io.FileUtils;
9 | import org.apache.http.client.methods.CloseableHttpResponse;
10 | import org.apache.http.client.methods.HttpGet;
11 | import org.apache.http.impl.client.CloseableHttpClient;
12 | import org.apache.http.impl.client.HttpClients;
13 |
14 | import java.io.File;
15 | import java.io.IOException;
16 |
17 | public class CmdToken extends CommandBase {
18 | @Override
19 | public String getName() {
20 | return "token";
21 | }
22 |
23 | @Override
24 | public String getUsage(ICommandSender sender) {
25 | return "存储并校验 wbelate 上的 token";
26 | }
27 |
28 | @Override
29 | public int getRequiredPermissionLevel() {
30 | return 0;
31 | }
32 |
33 | @Override
34 | public void execute(MinecraftServer server, ICommandSender sender, String[] args) {
35 | // 参数为空,警告
36 | if (args.length == 0) {
37 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.empty"));
38 | return;
39 | }
40 |
41 | // 不为空,开始测试
42 | new Thread(() -> {
43 | try {
44 | CloseableHttpResponse response = getTest(args[0]);
45 | switch (response.getStatusLine().getStatusCode()) {
46 | case 200:
47 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.200"));
48 | if (writeToken(args[0])) {
49 | return;
50 | } else {
51 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.error_write"));
52 | }
53 | return;
54 | case 400:
55 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.400"));
56 | return;
57 | case 401:
58 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.401"));
59 | return;
60 | case 403:
61 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.403"));
62 | return;
63 | case 404:
64 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.404"));
65 | return;
66 | case 429:
67 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.429"));
68 | return;
69 | default:
70 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.other", response.getStatusLine().getStatusCode()));
71 | }
72 | } catch (IOException ioe) {
73 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.upload_error"));
74 | }
75 | }, "I18n-Thread-Token-Test").start();
76 | }
77 |
78 | /**
79 | * GET 进行一次验证
80 | *
81 | * @param key weblate 的上传密匙
82 | * @return 校验完毕后返回的数据
83 | * @throws IOException 可能发生的 IO 错误
84 | */
85 | private CloseableHttpResponse getTest(String key) throws IOException {
86 | // API 地址
87 | String apiURL = "https://weblate.exz.me/api/";
88 |
89 | // 建立连接
90 | CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
91 |
92 | // Get 请求,同时设置验证和接收数据格式
93 | HttpGet httpGet = new HttpGet(apiURL);
94 | httpGet.addHeader("Authorization", "Token " + key);
95 | httpGet.addHeader("Accept", "application/json");
96 |
97 | // 执行
98 | CloseableHttpResponse response = closeableHttpClient.execute(httpGet);
99 | closeableHttpClient.close();
100 |
101 | return response;
102 | }
103 |
104 | /**
105 | * 将正确的 token 存储为文件
106 | *
107 | * @param key 传入的正确 token
108 | * @return 是否写入成功
109 | */
110 | private Boolean writeToken(String key) {
111 | File tokenFile = new File(Minecraft.getMinecraft().mcDataDir.toString() + File.separator + "config" + File.separator + "TOKEN.txt");
112 |
113 | // 删除文件
114 | if (tokenFile.exists()) {
115 | FileUtils.deleteQuietly(tokenFile);
116 | }
117 |
118 | // 写入文件
119 | try {
120 | if (tokenFile.createNewFile()) {
121 | FileUtils.write(tokenFile, key, "UTF-8");
122 | return true;
123 | }
124 | } catch (IOException ioe) {
125 | ioe.printStackTrace();
126 | }
127 | return false;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/command/CmdUpload.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.command;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.reflect.TypeToken;
5 | import net.minecraft.client.Minecraft;
6 | import net.minecraft.command.CommandBase;
7 | import net.minecraft.command.ICommandSender;
8 | import net.minecraft.server.MinecraftServer;
9 | import net.minecraft.util.math.BlockPos;
10 | import net.minecraft.util.text.TextComponentTranslation;
11 | import org.apache.commons.io.FileUtils;
12 | import org.apache.commons.io.IOUtils;
13 | import org.apache.http.client.methods.CloseableHttpResponse;
14 | import org.apache.http.client.methods.HttpPost;
15 | import org.apache.http.entity.mime.MultipartEntityBuilder;
16 | import org.apache.http.impl.client.CloseableHttpClient;
17 | import org.apache.http.impl.client.HttpClients;
18 | import org.cfpa.i18nupdatemod.I18nUtils;
19 |
20 | import javax.annotation.Nullable;
21 | import java.io.File;
22 | import java.io.IOException;
23 | import java.nio.charset.StandardCharsets;
24 | import java.util.ArrayList;
25 | import java.util.HashMap;
26 | import java.util.List;
27 | import java.util.Map;
28 | import java.util.regex.Matcher;
29 | import java.util.regex.Pattern;
30 |
31 | public class CmdUpload extends CommandBase {
32 | private boolean haveResponse;
33 |
34 | @Override
35 | public String getName() {
36 | return "lang_upload";
37 | }
38 |
39 | @Override
40 | public String getUsage(ICommandSender sender) {
41 | return "上传汉化文件到 weblate";
42 | }
43 |
44 | @Override
45 | public int getRequiredPermissionLevel() {
46 | return 0;
47 | }
48 |
49 | @Override
50 | public void execute(MinecraftServer server, ICommandSender sender, String[] args) {
51 | List modidList = getLangpackName();
52 | String key = I18nUtils.readToken();
53 |
54 | // 参数为空,警告
55 | if (args.length == 0) {
56 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.empty"));
57 | return;
58 | }
59 |
60 | // 参数存在,进行下一步判定
61 | if (modidList.contains(args[0])) {
62 | if (key == null) {
63 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.no_token", args[0]));
64 | return;
65 | }
66 |
67 | new Thread(() -> {
68 | try {
69 | postFile(args[0], key);
70 | } catch (Exception e) {
71 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.upload_error"));
72 | e.printStackTrace();
73 | }
74 | }, "I18n-Thread-File-Upload").start();
75 | }
76 | // 参数不存在,警告
77 | else {
78 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.not_found", args[0]));
79 | }
80 | }
81 |
82 | @Override
83 | public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos) {
84 | List modidList = getLangpackName();
85 |
86 | // 如果输入参数为空,返回整个列表
87 | if (args.length == 0) {
88 | return modidList;
89 | }
90 |
91 | // 如果输入不为空,从头字符串检索,进行输出
92 | List availableArgs = new ArrayList<>();
93 | for (String modid : modidList) {
94 | if (modid.indexOf(args[0]) == 0) {
95 | availableArgs.add(modid);
96 | }
97 | }
98 | return availableArgs;
99 | }
100 |
101 | /**
102 | * 检索资源包列表,获取语言文件地址
103 | *
104 | * @return 获取的语言文件列表
105 | */
106 | private List getLangpackName() {
107 | List outputModid = new ArrayList<>();
108 | File resourcepacksDir = new File(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString());
109 | if (resourcepacksDir.exists() && resourcepacksDir.isDirectory()) {
110 | for (File i : resourcepacksDir.listFiles()) {
111 | Matcher matcher = Pattern.compile("resourcepacks" + Matcher.quoteReplacement(File.separator) + "(.*?)_tmp_resource_pack").matcher(i.toString());
112 | while (matcher.find()) {
113 | File transFile = new File(i.toString() + File.separator + "assets" + File.separator + matcher.group(1) + File.separator + "lang" + File.separator + "zh_cn.lang");
114 | if (transFile.exists() && transFile.isFile()) {
115 | outputModid.add(matcher.group(1));
116 | }
117 | }
118 | }
119 | }
120 | return outputModid;
121 | }
122 |
123 | /**
124 | * POST 表单提交,将文件上传到 weblate 对应仓库
125 | *
126 | * @param modid 上传的模组资源 domain
127 | * @param key weblate 的上传密匙
128 | */
129 | private void postFile(String modid, String key) {
130 | // API 地址
131 | String apiURL = "https://weblate.exz.me/api/translations/langpack/" + modid + "/zh_cn/file/";
132 |
133 | // 将要上传的文件预处理
134 | File tmpFile;
135 | try {
136 | tmpFile = handleFile(modid);
137 | if (tmpFile == null) {
138 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.handle_error"));
139 | return;
140 | }
141 | } catch (IOException ioe) {
142 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.handle_error"));
143 | return;
144 | }
145 |
146 | // 建立连接
147 | CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
148 |
149 | // Post 请求,同时设置验证和接收数据格式
150 | HttpPost httpPost = new HttpPost(apiURL);
151 | httpPost.addHeader("Authorization", "Token " + key);
152 | httpPost.addHeader("Accept", "application/json");
153 |
154 | // 装填表单本体
155 | httpPost.setEntity(
156 | MultipartEntityBuilder.create()
157 | .addTextBody("overwrite", "true")
158 | .addBinaryBody("file", tmpFile)
159 | .build()
160 | );
161 |
162 | // 执行,并在执行完毕后,关闭连接
163 | CloseableHttpResponse response;
164 | try {
165 | // 先来一句提醒
166 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.excute_ready"));
167 |
168 | // 开始拨动线程检查
169 | haveResponse = false;
170 | new Thread(() -> {
171 | // 计数器
172 | int count = 0;
173 | while (!haveResponse) {
174 | try {
175 | // 阻塞
176 | Thread.sleep(5 * 1000);
177 | // 计数
178 | count = count + 5;
179 | // 二次检查显示信息
180 | if (!haveResponse) {
181 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.uploading", count));
182 | }
183 | } catch (InterruptedException e) {
184 | e.printStackTrace();
185 | }
186 | }
187 | }, "I18n-Upload-Check-Thread").start();
188 |
189 | // 执行上传!
190 | response = closeableHttpClient.execute(httpPost);
191 |
192 | // 依据返回结果进行不同显示
193 | switch (response.getStatusLine().getStatusCode()) {
194 | case 200:
195 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.200"));
196 | try {
197 | Gson gson = new Gson();
198 | POJOResult result = gson.fromJson(IOUtils.readLines(response.getEntity().getContent(), "UTF-8").get(0), new TypeToken() {
199 | }.getType());
200 |
201 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.result_title"));
202 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.result_count", result.getCount()));
203 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.result_total", result.getTotal()));
204 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.result_accepted", result.getAccepted()));
205 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.result_not_found", result.getNot_found()));
206 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.result_skipped", result.getSkipped()));
207 | } catch (IOException ioe) {
208 | ioe.printStackTrace();
209 | }
210 | break;
211 | case 400:
212 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.400"));
213 | break;
214 | case 401:
215 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.401"));
216 | break;
217 | case 403:
218 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.403"));
219 | break;
220 | case 404:
221 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.404"));
222 | break;
223 | case 429:
224 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.429"));
225 | break;
226 | case 524:
227 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.524"));
228 | break;
229 | default:
230 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_token.other", response.getStatusLine().getStatusCode()));
231 | break;
232 | }
233 |
234 | // 关闭提醒功能
235 | haveResponse = true;
236 |
237 | // 关闭网络连接
238 | closeableHttpClient.close();
239 | } catch (IOException ioe) {
240 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.upload_error"));
241 | }
242 | }
243 |
244 | /**
245 | * 将准备上传的语言文件进行处理
246 | *
247 | * @param modid 上传的模组资源 domain
248 | * @return 处理后的文件对象
249 | * @throws IOException 读取文件可能发生的 IO 错误
250 | */
251 | @Nullable
252 | private File handleFile(String modid) throws IOException {
253 | // 英文,中文,临时文件
254 | File rawChineseFile = new File(String.format(Minecraft.getMinecraft().mcDataDir.toString() + File.separator + "resourcepacks" + File.separator + "%s_tmp_resource_pack" + File.separator + "assets" + File.separator + "%s" + File.separator + "lang" + File.separator + "zh_cn.lang", modid, modid));
255 | File rawEnglishFile = new File(String.format(Minecraft.getMinecraft().mcDataDir.toString() + File.separator + "resourcepacks" + File.separator + "%s_tmp_resource_pack" + File.separator + "assets" + File.separator + "%s" + File.separator + "lang" + File.separator + "en_us.lang", modid, modid));
256 | File handleChineseFile = new File(String.format(Minecraft.getMinecraft().mcDataDir.toString() + File.separator + "resourcepacks" + File.separator + "%s_tmp_resource_pack" + File.separator + "assets" + File.separator + "%s" + File.separator + "lang" + File.separator + "zh_cn_tmp.lang", modid, modid));
257 |
258 | // 文件存在,才进行处理
259 | if (rawEnglishFile.exists() && rawChineseFile.exists()) {
260 | // 读取处理成 HashMap
261 | Map zh_cn = I18nUtils.listToMap(FileUtils.readLines(rawChineseFile, StandardCharsets.UTF_8));
262 | Map en_us = I18nUtils.listToMap(FileUtils.readLines(rawEnglishFile, StandardCharsets.UTF_8));
263 |
264 | // 未翻译处进行剔除
265 | List tmpFile = new ArrayList<>();
266 | for (String key : zh_cn.keySet()) {
267 | if (en_us.get(key) != null && !en_us.get(key).equals(zh_cn.get(key))) {
268 | tmpFile.add(key + '=' + zh_cn.get(key));
269 | }
270 | }
271 |
272 | // 文件写入
273 | FileUtils.writeLines(handleChineseFile, "UTF-8", tmpFile, "\n", false);
274 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_upload.handle_success"));
275 | return handleChineseFile;
276 | } else {
277 | return null; // 不存在返回 null
278 | }
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/command/POJOResult.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.command;
2 |
3 | public class POJOResult {
4 | private int count;
5 | private int accepted;
6 | private int skipped;
7 | private boolean result;
8 | private int not_found;
9 | private int total;
10 |
11 | public int getCount() {
12 | return count;
13 | }
14 |
15 | public int getAccepted() {
16 | return accepted;
17 | }
18 |
19 | public int getSkipped() {
20 | return skipped;
21 | }
22 |
23 | public boolean isResult() {
24 | return result;
25 | }
26 |
27 | public int getNot_found() {
28 | return not_found;
29 | }
30 |
31 | public int getTotal() {
32 | return total;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/command/package-info.java:
--------------------------------------------------------------------------------
1 | @MethodsReturnNonnullByDefault
2 | @ParametersAreNonnullByDefault
3 | package org.cfpa.i18nupdatemod.command;
4 |
5 | import mcp.MethodsReturnNonnullByDefault;
6 |
7 | import javax.annotation.ParametersAreNonnullByDefault;
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/download/DownloadInfoHelper.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.download;
2 |
3 | import net.minecraft.client.Minecraft;
4 | import net.minecraft.util.text.TextComponentTranslation;
5 |
6 | import java.util.Queue;
7 | import java.util.concurrent.ConcurrentLinkedQueue;
8 |
9 | public class DownloadInfoHelper {
10 | public static Queue info = new ConcurrentLinkedQueue<>();
11 |
12 | public static void init() {
13 | // 消息通知线程
14 | new Thread(() -> {
15 | while (true) {
16 | if (Minecraft.getMinecraft().player != null) {
17 | while (!info.isEmpty()) {
18 | String theInfo = info.remove();
19 | Minecraft.getMinecraft().addScheduledTask(() -> Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("[I18nUpdateMod] " + theInfo)));
20 | }
21 | }
22 | try {
23 | Thread.sleep(1000);
24 | } catch (InterruptedException e) {
25 | e.printStackTrace();
26 | }
27 | }
28 | }, "I18n-download-info-Thread").start();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/download/DownloadStatus.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.download;
2 |
3 | public enum DownloadStatus {
4 | IDLE,
5 | SUCCESS,
6 | DOWNLOADING,
7 | FAIL,
8 | CANCELED,
9 | BACKGROUND
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/download/DownloadWindow.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.download;
2 |
3 | import org.cfpa.i18nupdatemod.I18nConfig;
4 |
5 | import javax.swing.*;
6 | import javax.swing.border.EmptyBorder;
7 | import javax.swing.text.JTextComponent;
8 |
9 | import java.awt.*;
10 | import java.awt.event.ActionEvent;
11 |
12 | public class DownloadWindow {
13 | private IDownloadManager manager;
14 | private JFrame frame;
15 | private JProgressBar bar;
16 |
17 | /**
18 | * 弹出一个窗口,包含一个进度条显示下载的进度
19 | *
20 | * @param manager 对应的DownloadManager对象
21 | */
22 | public DownloadWindow(IDownloadManager manager) {
23 | this.manager = manager;
24 | init();
25 | }
26 |
27 | private void init() {
28 | // 初始化窗口
29 | frame = new JFrame();
30 | GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
31 | int width = gd.getDisplayMode().getWidth() >= 1920 ? (int) (gd.getDisplayMode().getWidth() * 0.2) : 384;
32 | int height = (int) (width * 0.4);
33 | frame.setBounds((Toolkit.getDefaultToolkit().getScreenSize().width - width) / 2,
34 | (Toolkit.getDefaultToolkit().getScreenSize().height - height) / 25 * 10, width, height);
35 | frame.setTitle(I18nConfig.download.dlWindowsName);
36 | JPanel contentPane = new JPanel();
37 | contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
38 | frame.setContentPane(contentPane);
39 | contentPane.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
40 |
41 | JLabel label = new JLabel("");
42 | contentPane.add(label);
43 | // 绘制进度条
44 | bar = new JProgressBar();
45 | bar.setPreferredSize(new Dimension(width / 5 * 4, height / 5));
46 | bar.setStringPainted(true);
47 | contentPane.add(bar);
48 | // 在下载未完成时禁止玩家关闭窗口
49 | frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
50 |
51 | // 取消按钮
52 | JButton btCancel = new JButton("取消下载") {
53 | @Override
54 | protected void fireActionPerformed(ActionEvent event) {
55 | super.fireActionPerformed(event);
56 | manager.cancel();
57 | frame.setVisible(false);
58 | }
59 | };
60 | btCancel.setLayout(new GridLayout(3, 2, 5, 5));
61 | btCancel.setSize(width / 5, height / 5);
62 | contentPane.add(btCancel);
63 |
64 | // 后台下载按钮
65 | JButton btBackground = new JButton("后台下载") {
66 | @Override
67 | protected void fireActionPerformed(ActionEvent event) {
68 | super.fireActionPerformed(event);
69 | manager.background();
70 | frame.setVisible(false);
71 | }
72 | };
73 | btBackground.setLayout(new GridLayout(3, 2, 5, 5));
74 | btBackground.setSize(width / 5, height / 5);
75 | contentPane.add(btBackground);
76 |
77 | // 进度条更新线程
78 | new Thread(() -> {
79 | while (!manager.isDone()) {
80 | bar.setValue((int) (manager.getCompletePercentage() * 100));
81 | label.setText(manager.getTaskTitle());
82 | try {
83 | Thread.sleep(20);
84 | } catch (InterruptedException ignore) {
85 |
86 | }
87 | }
88 | onDownloadFinish();
89 | if (manager.getStatus() == DownloadStatus.FAIL) {
90 | bar.setString("下载失败!");
91 | try {
92 | Thread.sleep(1500);
93 | } catch (InterruptedException ignore) {
94 | }
95 | frame.setVisible(false);
96 | } else {
97 | // 如果下载完成自动关闭窗口
98 | frame.setVisible(false);
99 | }
100 | }, "I18n-Window-Thread").start();
101 |
102 | // 超时守护进程
103 | new Thread(() -> {
104 | try {
105 | while (manager.getStatus() == DownloadStatus.IDLE)
106 | Thread.sleep(50);
107 | int i = I18nConfig.download.maxTime;
108 | while (!manager.isDone() && i >= 0) {
109 | btBackground.setText("后台下载(" + i + ')');
110 | Thread.sleep(1000);
111 | if (i == 0) {
112 | // 如果超时就隐藏窗口到后台下载并停止阻塞主线程
113 | background();
114 | }
115 | i--;
116 | }
117 | } catch (Throwable ignore) {
118 | }
119 | }).start();
120 | }
121 |
122 | /**
123 | * 显示窗口
124 | */
125 | public void showWindow() {
126 | frame.setVisible(true);
127 | }
128 |
129 | /**
130 | * 后台下载
131 | */
132 | public void background() {
133 | frame.setVisible(false);
134 | manager.background();
135 | }
136 |
137 | private void onDownloadFinish() {
138 | if (!frame.isVisible()) {
139 | if (manager.getStatus() == DownloadStatus.SUCCESS) {
140 | DownloadInfoHelper.info.add("资源包后台下载完成,下次重启游戏将加载资源包");
141 | } else {
142 | DownloadInfoHelper.info.add("资源包后台下载失败");
143 | }
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/download/FileDownloadManager.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.download;
2 |
3 | import org.cfpa.i18nupdatemod.I18nUpdateMod;
4 |
5 | import java.io.*;
6 | import java.net.URL;
7 | import java.net.URLConnection;
8 |
9 | public class FileDownloadManager implements IDownloadManager {
10 | private Thread downloadThread;
11 | private MainDownloader downloader;
12 | private DownloadStatus status = DownloadStatus.IDLE;
13 |
14 | /**
15 | * 下载管理器
16 | *
17 | * @param urlIn 下载目标的URL地址
18 | * @param fileNameIn 存储文件的名字
19 | * @param dirIn 存储文件的地址
20 | */
21 | public FileDownloadManager(String urlIn, String fileNameIn, String dirIn) {
22 | try {
23 | downloader = new MainDownloader(urlIn, fileNameIn, dirIn);
24 | } catch (IOException e) {
25 | catching(e);
26 | }
27 | }
28 |
29 | public void setSuccessTask(Runnable successTask) {
30 | this.downloader.successTask = successTask;
31 | }
32 |
33 | /**
34 | * 开始下载
35 | *
36 | * @param threadName 线程名称
37 | */
38 | public void start(String threadName) {
39 | status = DownloadStatus.DOWNLOADING;
40 | downloadThread = new Thread(() -> {
41 | try {
42 | downloader.downloadResource();
43 | } catch (Throwable e) {
44 | catching(e);
45 | }
46 | }, threadName);
47 | downloadThread.start();
48 | }
49 |
50 | public void cancel() {
51 | downloader.done = true;
52 | status = DownloadStatus.CANCELED;
53 | downloader.alive = false;
54 | }
55 |
56 | public void background() {
57 | status = DownloadStatus.BACKGROUND;
58 | }
59 |
60 | private void catching(Throwable e) {
61 | I18nUpdateMod.logger.error("下载失败", e);
62 | DownloadInfoHelper.info.add("资源包更新失败。");
63 | status = DownloadStatus.FAIL;
64 | downloader.done = true;
65 | }
66 |
67 | /**
68 | * 获得下载的状态
69 | * SUCCESS:下载成功
70 | * DOWNLOADING:正在下载
71 | * FAIL:下载遇到错误
72 | * CANCELED:下载被玩家取消
73 | *
74 | * @return 下载状态
75 | */
76 | public DownloadStatus getStatus() {
77 | if ((status == DownloadStatus.DOWNLOADING || status == DownloadStatus.BACKGROUND) && downloader.done) {
78 | status = DownloadStatus.SUCCESS;
79 | }
80 | return status;
81 | }
82 |
83 | /**
84 | * 下载是否结束
85 | *
86 | * @return 下载是否结束
87 | */
88 | public boolean isDone() {
89 | return downloader.done;
90 | }
91 |
92 | /**
93 | * 获得下载完成百分比
94 | *
95 | * @return 下载完成的百分比
96 | */
97 | public float getCompletePercentage() {
98 | return downloader.completePercentage;
99 | }
100 |
101 | private static class MainDownloader {
102 | private URL url;
103 | private String fileName;
104 | private String dirPlace;
105 | private int size = 0;
106 | private int downloadedSize = 0;
107 | private boolean done = false;
108 |
109 | Runnable successTask;
110 |
111 | public float completePercentage = 0.0F;
112 | public boolean alive = true;
113 |
114 | MainDownloader(String urlIn, String fileName, String dirPlace) throws IOException {
115 | this.url = new URL(urlIn);
116 | this.fileName = fileName;
117 | this.dirPlace = dirPlace;
118 | }
119 |
120 | void downloadResource() throws Throwable {
121 | // 建立链接
122 | URLConnection connection = url.openConnection();
123 |
124 | // 超时
125 | connection.setConnectTimeout(10 * 1000);
126 |
127 | // 获取文件总大小
128 | size = connection.getContentLength();
129 |
130 | // 开始获取输入流
131 | InputStream inputStream = connection.getInputStream();
132 | byte[] getData = readInputStream(inputStream);
133 |
134 | // 文件保存位置
135 | if (getData != null) {
136 | File saveDir = new File(dirPlace);
137 | if (!saveDir.exists()) {
138 | saveDir.mkdir();
139 | }
140 | File file = new File(saveDir + File.separator + fileName);
141 | FileOutputStream fos = new FileOutputStream(file);
142 |
143 | fos.write(getData);
144 | fos.close();
145 | inputStream.close();
146 | }
147 | done = true;
148 | if (successTask != null) {
149 | successTask.run();
150 | }
151 | }
152 |
153 | private byte[] readInputStream(InputStream inputStream) throws IOException {
154 | byte[] buffer = new byte[64];
155 | int len;
156 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
157 | while ((len = inputStream.read(buffer)) != -1) {
158 | bos.write(buffer, 0, len);
159 | downloadedSize += len;
160 | completePercentage = (float) downloadedSize / (float) size;
161 | if (!alive) {
162 | return null;
163 | }
164 | }
165 | bos.close();
166 | return bos.toByteArray();
167 | }
168 | }
169 |
170 | @Override
171 | public String getTaskTitle() {
172 | return "正在下载...";
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/download/IDownloadManager.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.download;
2 |
3 | public interface IDownloadManager {
4 |
5 | void cancel();
6 |
7 | void background();
8 |
9 | boolean isDone();
10 |
11 | DownloadStatus getStatus();
12 |
13 | float getCompletePercentage();
14 |
15 | String getTaskTitle();
16 |
17 | public void start(String threadName);
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/download/RepoUpdateManager.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.download;
2 |
3 | import java.io.File;
4 |
5 | import org.cfpa.i18nupdatemod.I18nUpdateMod;
6 | import org.cfpa.i18nupdatemod.git.ResourcePackRepository;
7 | import org.cfpa.i18nupdatemod.resourcepack.AssetMap;
8 | import org.eclipse.jgit.lib.ProgressMonitor;
9 |
10 | public class RepoUpdateManager implements IDownloadManager {
11 | private ResourcePackRepository repo;
12 | private DownloadStatus status = DownloadStatus.IDLE;
13 | private boolean done = false;
14 | private ProgressMonitor monitor;
15 | private boolean cancelled = false;
16 | private String taskTitle = "等待...";
17 | private float completePercentage = 0;
18 |
19 | public void update() {
20 | DownloadWindow window = new DownloadWindow(this);
21 | window.showWindow();
22 | this.start("I18n-Download-Thread");
23 | while (this.getStatus() == DownloadStatus.DOWNLOADING) {
24 | try {
25 | Thread.sleep(50);
26 | } catch (InterruptedException ignore) {
27 | }
28 | }
29 | }
30 |
31 | class simpleProgressMonitor implements ProgressMonitor {
32 | int totalTasks = 1;
33 | int totalWork = 1;
34 | int curTask = 0;
35 | int completed = 0;
36 |
37 | @Override
38 | public void beginTask(String title, int totalWork) {
39 | // TODO L10n
40 | taskTitle = title;
41 | completed = 0;
42 | this.totalWork = totalWork;
43 | }
44 |
45 | @Override
46 | public void endTask() {
47 | curTask += 1;
48 | }
49 |
50 | @Override
51 | public boolean isCancelled() {
52 | return cancelled;
53 | }
54 |
55 | @Override
56 | public void start(int totalTasks) {
57 | this.totalTasks = totalTasks;
58 | }
59 |
60 | @Override
61 | public void update(int completed) {
62 | this.completed += completed;
63 | completePercentage = (float) this.completed / (float) totalWork;
64 | }
65 |
66 | }
67 |
68 | public RepoUpdateManager(ResourcePackRepository repo) {
69 | this.repo = repo;
70 | monitor = new simpleProgressMonitor();
71 | }
72 |
73 | @Override
74 | public void cancel() {
75 | status = DownloadStatus.CANCELED;
76 | this.cancelled = true;
77 | }
78 |
79 | @Override
80 | public void background() {
81 | status = DownloadStatus.BACKGROUND;
82 |
83 | }
84 |
85 | @Override
86 | public boolean isDone() {
87 | return this.done;
88 | }
89 |
90 | @Override
91 | public DownloadStatus getStatus() {
92 | if ((status == DownloadStatus.DOWNLOADING || status == DownloadStatus.BACKGROUND) && this.done) {
93 | status = DownloadStatus.SUCCESS;
94 | }
95 | return status;
96 | }
97 |
98 | @Override
99 | public float getCompletePercentage() {
100 | return completePercentage;
101 | }
102 |
103 | @Override
104 | public String getTaskTitle() {
105 | return taskTitle;
106 | }
107 |
108 | @Override
109 | public void start(String threadName) {
110 | status = DownloadStatus.DOWNLOADING;
111 | Thread downloadThread = new Thread(() -> {
112 | try {
113 | repo.fetch(monitor);
114 | repo.reset(monitor);
115 | repo.sparseCheckout(ResourcePackRepository.getSubPathOfAsset("i18nmod"), monitor);
116 | File assetMap = new File(repo.getLocalPath(), "assets/i18nmod/asset_map/asset_map.json");
117 | if (assetMap.exists())
118 | AssetMap.instance().update(assetMap);
119 | repo.sparseCheckout(repo.getSubPaths(), monitor);
120 | repo.close();
121 | this.done = true;
122 | } catch (Throwable e) {
123 | I18nUpdateMod.logger.error("Error while downloading: ", e);
124 | }
125 | }, threadName);
126 | downloadThread.start();
127 |
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/git/ResourcePackRepository.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.git;
2 |
3 | import static org.cfpa.i18nupdatemod.I18nUpdateMod.logger;
4 |
5 | import java.io.File;
6 | import java.util.ArrayList;
7 | import java.util.Collection;
8 | import java.util.List;
9 | import java.util.Set;
10 | import java.util.stream.Collectors;
11 |
12 | import org.apache.http.client.methods.CloseableHttpResponse;
13 | import org.apache.http.client.methods.HttpGet;
14 | import org.apache.http.impl.client.CloseableHttpClient;
15 | import org.apache.http.impl.client.HttpClients;
16 | import org.apache.http.util.EntityUtils;
17 | import org.cfpa.i18nupdatemod.I18nConfig;
18 | import org.eclipse.jgit.api.Git;
19 | import org.eclipse.jgit.api.CheckoutCommand;
20 | import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
21 | import org.eclipse.jgit.api.ResetCommand.ResetType;
22 | import org.eclipse.jgit.lib.ProgressMonitor;
23 | import org.eclipse.jgit.lib.StoredConfig;
24 | import org.eclipse.jgit.transport.RemoteConfig;
25 |
26 | public class ResourcePackRepository {
27 | private static String[] remoteURLs;
28 | private File localPath;
29 | public Git gitRepo = null;
30 | private String branch;
31 | private Set assetDomains;
32 |
33 | public ResourcePackRepository(String localPath, Set assetDomains) {
34 | remoteURLs = I18nConfig.download.remoteRepoURL;
35 | this.localPath = new File(localPath);
36 | this.assetDomains = assetDomains;
37 | this.branch = "1.12.2-release";
38 | initRepo();
39 | }
40 |
41 | private void initRepo() {
42 | if (localPath.exists()) {
43 | try {
44 | gitRepo = Git.open(localPath);
45 | } catch (Exception e) {
46 | logger.error("Exception caught while initializing git repository: ", e);
47 | }
48 | }
49 | if (gitRepo == null) {
50 | try {
51 | gitRepo = Git.init().setDirectory(localPath).call();
52 | } catch (Exception e) {
53 | e.printStackTrace();
54 | }
55 | }
56 | }
57 |
58 | private void configRemote(String name, String url, String branchName) {
59 | try {
60 | StoredConfig config = gitRepo.getRepository().getConfig();
61 | config.setString("remote", name, "url", url);
62 | config.setString("remote", name, "fetch",
63 | "+refs/heads/" + branchName + ":refs/remotes/origin/" + branchName);
64 | config.save();
65 | } catch (Exception e) {
66 | e.printStackTrace();
67 | }
68 | }
69 |
70 | public void close() {
71 | gitRepo.getRepository().close();
72 | }
73 |
74 | private boolean fetchFromRemote(String remoteName, ProgressMonitor monitor) {
75 | try {
76 | // fetch
77 | gitRepo.fetch()
78 | .setProgressMonitor(monitor)
79 | .setRemote(remoteName)
80 | .call();
81 | return true;
82 | } catch (Exception e) {
83 | logger.error("Invalid remote repository: ", e);
84 | return false;
85 | }
86 | }
87 |
88 | public void fetch(ProgressMonitor monitor) {
89 | boolean success;
90 | List remoteList = new ArrayList<>();
91 | try {
92 | remoteList = gitRepo.remoteList().call();
93 | } catch (Exception e) {
94 | logger.error("Error while getting remote list: ", e);
95 | }
96 | for (RemoteConfig remoteConfig : remoteList) {
97 | // TODO 检查连接情况
98 | success = fetchFromRemote(remoteConfig.getName(), monitor);
99 | if (success) {
100 | return;
101 | }
102 | }
103 |
104 | for (int i = 0; i < remoteURLs.length; i++) {
105 | String remoteName = "origin" + i;
106 | String gitURL = remoteURLs[i];
107 |
108 | // 解析 HTML 重定向,HTTP 302 重定向用不了
109 | if (remoteURLs[i].endsWith("html") || remoteURLs[i].endsWith("htm")) {
110 | CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
111 | HttpGet httpGet = new HttpGet(remoteURLs[i]);
112 | CloseableHttpResponse response;
113 | try {
114 | response = closeableHttpClient.execute(httpGet);
115 | if (response.getStatusLine().getStatusCode() == 200) {
116 | String result = EntityUtils.toString(response.getEntity(), "UTF-8");
117 | int index = result.indexOf("meta http-equiv=\"refresh\"");
118 | int startIndex = index + "meta http-equiv=\"refresh\" content=\"0;url=".length();
119 | int endIndex = result.indexOf("\">", startIndex);
120 | gitURL = result.substring(startIndex, endIndex);
121 | closeableHttpClient.close();
122 | } else {
123 | closeableHttpClient.close();
124 | continue;
125 | }
126 | } catch (Exception e) {
127 | logger.error("Error while fetching resource pack repository: ", e);
128 | continue;
129 | }
130 | }
131 |
132 | configRemote(remoteName, gitURL, this.branch);
133 | success = fetchFromRemote(remoteName, monitor);
134 |
135 | if (success) {
136 | return;
137 | }
138 | }
139 | logger.warn("仓库更新失败");
140 | }
141 |
142 | public void reset(ProgressMonitor monitor) {
143 | try {
144 | // create branch and set upstream
145 | gitRepo.branchCreate()
146 | .setName(branch)
147 | .setUpstreamMode(SetupUpstreamMode.SET_UPSTREAM)
148 | .setStartPoint("origin/" + branch)
149 | .setForce(true)
150 | .call();
151 |
152 | // reset to remote head
153 | gitRepo.reset()
154 | .setProgressMonitor(monitor)
155 | .setMode(ResetType.SOFT)
156 | .setRef("refs/remotes/origin/" + branch)
157 | .call();
158 | } catch (Exception e) {
159 | logger.error("Exception caught while reseting to remote head: ", e);
160 | }
161 | }
162 |
163 | public void sparseCheckout(Collection subPathSet, ProgressMonitor monitor) {
164 | try {
165 | // sparse checkout
166 | CheckoutCommand checkoutCommand = gitRepo.checkout();
167 |
168 | checkoutCommand.setProgressMonitor(monitor)
169 | .setName(branch)
170 | .setStartPoint(branch);
171 |
172 | subPathSet.forEach(checkoutCommand::addPath);
173 | checkoutCommand.call();
174 | } catch (Exception e) {
175 | logger.error("Exception caught while checking out: ", e);
176 | }
177 | }
178 |
179 | public void sparseCheckout(String subPath, ProgressMonitor monitor) {
180 | try {
181 | // sparse checkout
182 | gitRepo.checkout()
183 | .setProgressMonitor(monitor)
184 | .setName(branch)
185 | .setStartPoint(branch)
186 | .addPath(subPath)
187 | .call();
188 | } catch (Exception e) {
189 | logger.error("Exception caught while checking out: ", e);
190 | }
191 | }
192 |
193 | public static String getSubPathOfAsset(String domain) {
194 | return "assets/" + domain;
195 | }
196 |
197 | public Collection getSubPaths() {
198 | return assetDomains.stream().map(ResourcePackRepository::getSubPathOfAsset).collect(Collectors.toSet());
199 | }
200 |
201 | public File getLocalPath() {
202 | return localPath;
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/hotkey/HotKeyHandler.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.hotkey;
2 |
3 | import mezz.jei.Internal;
4 | import mezz.jei.gui.overlay.ItemListOverlay;
5 | import mezz.jei.runtime.JeiRuntime;
6 | import net.minecraft.client.Minecraft;
7 | import net.minecraft.client.gui.GuiScreen;
8 | import net.minecraft.client.gui.inventory.GuiContainer;
9 | import net.minecraft.client.settings.KeyBinding;
10 | import net.minecraft.init.Items;
11 | import net.minecraft.inventory.Slot;
12 | import net.minecraft.item.ItemStack;
13 | import net.minecraft.util.text.TextComponentTranslation;
14 | import net.minecraftforge.client.event.GuiScreenEvent;
15 | import net.minecraftforge.fml.client.registry.ClientRegistry;
16 | import net.minecraftforge.fml.common.Loader;
17 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
18 | import net.minecraftforge.fml.common.gameevent.InputEvent;
19 | import org.apache.commons.io.IOUtils;
20 | import org.cfpa.i18nupdatemod.I18nConfig;
21 | import org.cfpa.i18nupdatemod.I18nUpdateMod;
22 | import org.cfpa.i18nupdatemod.I18nUtils;
23 | import org.lwjgl.input.Keyboard;
24 |
25 | import java.awt.*;
26 | import java.io.UnsupportedEncodingException;
27 | import java.net.URI;
28 | import java.net.URL;
29 | import java.net.URLEncoder;
30 | import java.nio.charset.StandardCharsets;
31 |
32 | public final class HotKeyHandler {
33 | private final KeyBinding mainKey = new KeyBinding("key.i18nmod.main_key.desc", Keyboard.KEY_LCONTROL, "key.category.i18nmod");
34 | private final KeyBinding reportKey = new KeyBinding("key.i18nmod.report_key.desc", Keyboard.KEY_K, "key.category.i18nmod");
35 | private final KeyBinding weblateKey = new KeyBinding("key.i18nmod.weblate_key.desc", Keyboard.KEY_L, "key.category.i18nmod");
36 | private final KeyBinding mcmodKey = new KeyBinding("key.i18nmod.mcmod_key.desc", Keyboard.KEY_M, "key.category.i18nmod");
37 | private final KeyBinding reloadKey = new KeyBinding("key.i18nmod.reload_key.desc", Keyboard.KEY_R, "key.category.i18nmod");
38 |
39 | private boolean showed = false;
40 |
41 | public HotKeyHandler() {
42 | ClientRegistry.registerKeyBinding(mainKey);
43 | ClientRegistry.registerKeyBinding(reportKey);
44 | ClientRegistry.registerKeyBinding(weblateKey);
45 | ClientRegistry.registerKeyBinding(mcmodKey);
46 | ClientRegistry.registerKeyBinding(reloadKey);
47 | }
48 |
49 | // 在打开 GUI 情况下的按键触发
50 | @SubscribeEvent
51 | public void onKeyPress(GuiScreenEvent.KeyboardInputEvent.Pre event) {
52 | // 检测配置
53 | if (I18nConfig.key.closedKey || (I18nConfig.internationalization.openI18n && !I18nUtils.isChinese())) {
54 | return;
55 | }
56 |
57 | GuiScreen guiScreen = Minecraft.getMinecraft().currentScreen;
58 |
59 | // 取消重复显示
60 | if (showed) {
61 | try {
62 | if (!Keyboard.isKeyDown(reportKey.getKeyCode()) && !Keyboard.isKeyDown(weblateKey.getKeyCode()) && !Keyboard.isKeyDown(mcmodKey.getKeyCode()) && !Keyboard.isKeyDown(reloadKey.getKeyCode())) {
63 | showed = false;
64 | }
65 | } catch (IndexOutOfBoundsException ex) {
66 | showed = false;
67 | }
68 | return;
69 | }
70 |
71 | // 重载汉化
72 | if (reloadLocalization()) {
73 | showed = true;
74 | return;
75 | }
76 |
77 | // 原版判定
78 | if (guiScreen instanceof GuiContainer) {
79 | GuiContainer guiContainer = (GuiContainer) guiScreen;
80 | Slot slotUnderMouse = guiContainer.getSlotUnderMouse();
81 | if (slotUnderMouse != null) {
82 | showed = handleKey(slotUnderMouse.getStack());
83 | return;
84 | }
85 | }
86 |
87 | // JEI 支持
88 | if (Loader.isModLoaded("jei")) {
89 | try {
90 | ItemListOverlay listOverlay = (ItemListOverlay) JeiRuntime.class.getMethod("getItemListOverlay").invoke(Internal.getRuntime());
91 | showed = handleKey(listOverlay.getStackUnderMouse());
92 | } catch (Throwable ex) {
93 | I18nUpdateMod.logger.warn("Unable to get JEI item.", ex);
94 | }
95 | }
96 | }
97 |
98 | // 非 GUI 情况下的按键触发
99 | @SubscribeEvent
100 | public void onKeyPressNoGui(InputEvent.KeyInputEvent e) {
101 | // 最开始,检测是否启用国际化配置
102 | if (I18nConfig.internationalization.openI18n && !I18nUtils.isChinese()) {
103 | return;
104 | }
105 |
106 | // 接下来检测是否关闭键位
107 | if (I18nConfig.key.closedKey) {
108 | return;
109 | }
110 |
111 | // 取消重复显示
112 | if (showed) {
113 | if (keyCodeCheck(reloadKey.getKeyCode()) && !Keyboard.isKeyDown(reloadKey.getKeyCode())) {
114 | showed = false;
115 | }
116 | return;
117 | }
118 | showed = reloadLocalization();
119 | }
120 |
121 | /**
122 | * 获取物品信息,并打开浏览器
123 | *
124 | * @param stack 物品
125 | * @return 是否成功
126 | */
127 | private boolean openReport(ItemStack stack) {
128 | String text = String.format("模组ID:%s\n非本地化名称:%s\n显示名称:%s", stack.getItem().getCreatorModId(stack), stack.getItem().getUnlocalizedName(), stack.getDisplayName());
129 | String url = I18nConfig.key.reportURL;
130 | try {
131 | GuiScreen.setClipboardString(text);
132 | Desktop.getDesktop().browse(new URI(url));
133 | } catch (Exception e) {
134 | e.printStackTrace();
135 | return false;
136 | }
137 | return true;
138 | }
139 |
140 | /**
141 | * 获取物品信息,并打开 weblate 对应界面
142 | *
143 | * @param stack 物品
144 | * @return 是否成功
145 | */
146 | private boolean openWeblate(ItemStack stack) {
147 | String displayName, assetsName;
148 |
149 | // 先进行字符获取与转义
150 | try {
151 | displayName = URLEncoder.encode(stack.getDisplayName(), "UTF-8");
152 | assetsName = URLEncoder.encode(stack.getItem().getRegistryName().getResourceDomain(), "UTF-8");
153 | } catch (UnsupportedEncodingException e) {
154 | e.printStackTrace();
155 | return false;
156 | }
157 |
158 | // 打开对应连接
159 | String url = String.format("https://weblate.sayori.pw/translate/langpack/%s/zh_cn/?q=%s&search=substring&source=on&target=on", assetsName, displayName);
160 | try {
161 | Desktop.getDesktop().browse(new URI(url));
162 | } catch (Exception e) {
163 | e.printStackTrace();
164 | return false;
165 | }
166 | return true;
167 | }
168 |
169 | /**
170 | * 获取物品信息,并打开 mcmod 对应界面
171 | *
172 | * @param stack 物品
173 | * @return 是否成功
174 | */
175 | private boolean openMcmod(ItemStack stack) {
176 | String modName, regName, displayName, url;
177 | int metadata, mcmodApiNum;
178 |
179 | // 先进行字符获取与转义
180 | try {
181 | modName = URLEncoder.encode(stack.getItem().getCreatorModId(stack), "UTF-8");
182 | regName = URLEncoder.encode(stack.getItem().getRegistryName().toString(), "UTF-8");
183 | displayName = URLEncoder.encode(stack.getDisplayName(), "UTF-8");
184 | metadata = stack.getMetadata();
185 | } catch (Exception e) {
186 | e.printStackTrace();
187 | return false;
188 | }
189 |
190 | // 访问 mcmod 百科 api,获取物品对应 id
191 | try {
192 | URL apiUrl = new URL(String.format("https://api.mcmod.cn/getItem/?regname=%s&metadata=%d", regName, metadata));
193 | mcmodApiNum = Integer.parseInt(IOUtils.readLines(apiUrl.openStream(), StandardCharsets.UTF_8).get(0));
194 | } catch (Exception e) {
195 | e.printStackTrace();
196 | return false;
197 | }
198 |
199 | // 通过获取的 id 判定生成什么连接
200 | // 有则去往对应物品,无则尝试进行搜索
201 | url = mcmodApiNum > 0 ? String.format("https://www.mcmod.cn/item/%d.html", mcmodApiNum) : String.format("https://www.mcmod.cn/s?key=%s+%s&i18nmod=true", modName, displayName);
202 |
203 | // 打开对应连接
204 | try {
205 | Desktop.getDesktop().browse(new URI(url));
206 | } catch (Exception e) {
207 | I18nUpdateMod.logger.error("打开链接失败", e);
208 | return false;
209 | }
210 | return true;
211 | }
212 |
213 | /**
214 | * 获取输入按键,进行不同处理
215 | *
216 | * @param stack 物品
217 | * @return 是否成功
218 | */
219 | private boolean handleKey(ItemStack stack) {
220 | if (stack != null && stack != ItemStack.EMPTY && stack.getItem() != Items.AIR) {
221 | // 问题报告界面的打开
222 | if (keyCodeCheck(reportKey.getKeyCode()) && Keyboard.isKeyDown(mainKey.getKeyCode()) && Keyboard.getEventKey() == reportKey.getKeyCode()) {
223 | return openReport(stack);
224 | }
225 | // Weblate 翻译界面的打开
226 | else if (keyCodeCheck(weblateKey.getKeyCode()) && Keyboard.isKeyDown(mainKey.getKeyCode()) && Keyboard.getEventKey() == weblateKey.getKeyCode()) {
227 | return openWeblate(stack);
228 | }
229 | // mcmod 百科界面的打开
230 | else if (keyCodeCheck(mcmodKey.getKeyCode()) && Keyboard.isKeyDown(mainKey.getKeyCode()) && Keyboard.getEventKey() == mcmodKey.getKeyCode()) {
231 | return openMcmod(stack);
232 | }
233 | }
234 | return false;
235 | }
236 |
237 | /**
238 | * 单独功能,快速重载语言文件
239 | *
240 | * @return 是否成功
241 | */
242 | private boolean reloadLocalization() {
243 | if (keyCodeCheck(reportKey.getKeyCode()) && Keyboard.isKeyDown(mainKey.getKeyCode()) && Keyboard.getEventKey() == reloadKey.getKeyCode()) {
244 | Minecraft.getMinecraft().getLanguageManager().onResourceManagerReload(Minecraft.getMinecraft().getResourceManager());
245 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("message.i18nmod.cmd_reload.success"));
246 | return true;
247 | }
248 | return false;
249 | }
250 |
251 | private boolean keyCodeCheck(int keyCode) {
252 | return 1 < keyCode && keyCode < 256;
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/installer/ResourcePackInstaller.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.installer;
2 |
3 | import com.google.common.collect.Lists;
4 | import net.minecraft.client.Minecraft;
5 | import net.minecraft.client.resources.ResourcePackRepository;
6 | import net.minecraft.client.settings.GameSettings;
7 | import org.cfpa.i18nupdatemod.I18nConfig;
8 | import org.cfpa.i18nupdatemod.I18nUpdateMod;
9 |
10 | import java.io.File;
11 | import java.net.InetAddress;
12 | import java.net.URL;
13 | import java.util.ArrayList;
14 | import java.util.Iterator;
15 | import java.util.List;
16 |
17 | import static org.cfpa.i18nupdatemod.I18nUpdateMod.logger;
18 | import static org.cfpa.i18nupdatemod.I18nUtils.isChinese;
19 |
20 | public class ResourcePackInstaller {
21 |
22 | public static void setResourcesRepository() {
23 | Minecraft mc = Minecraft.getMinecraft();
24 | GameSettings gameSettings = mc.gameSettings;
25 | // 在gameSetting中加载资源包
26 | if (!gameSettings.resourcePacks.contains(I18nConfig.download.i18nLangPackName)) {
27 | if (I18nConfig.priority) {
28 | mc.gameSettings.resourcePacks.add(I18nConfig.download.i18nLangPackName);
29 | } else {
30 | List packs = new ArrayList<>(10);
31 | packs.add(I18nConfig.download.i18nLangPackName); // 资源包的 index 越小优先级越低(在资源包 gui 中置于更低层)
32 | packs.addAll(gameSettings.resourcePacks);
33 | gameSettings.resourcePacks = packs;
34 | }
35 | }
36 | reloadResources();
37 | }
38 |
39 | private static void reloadResources() {
40 | Minecraft mc = Minecraft.getMinecraft();
41 | GameSettings gameSettings = mc.gameSettings;
42 | // 因为这时候资源包已经加载了,所以需要重新读取,重新加载
43 | ResourcePackRepository resourcePackRepository = mc.getResourcePackRepository();
44 | resourcePackRepository.updateRepositoryEntriesAll();
45 | List repositoryEntriesAll = resourcePackRepository.getRepositoryEntriesAll();
46 | List repositoryEntries = Lists.newArrayList();
47 | Iterator it = gameSettings.resourcePacks.iterator();
48 |
49 | while (it.hasNext()) {
50 | String packName = it.next();
51 | for (ResourcePackRepository.Entry entry : repositoryEntriesAll) {
52 | if (entry.getResourcePackName().equals(packName)) {
53 | // packFormat 为 3,或者 incompatibleResourcePacks 条目中有的资源包才会加入
54 | if (entry.getPackFormat() == 3 || gameSettings.incompatibleResourcePacks.contains(entry.getResourcePackName())) {
55 | repositoryEntries.add(entry);
56 | break;
57 | }
58 | // 否则移除
59 | it.remove();
60 | logger.warn("移除资源包 {},因为它无法兼容当前版本", entry.getResourcePackName());
61 | }
62 | }
63 | }
64 |
65 | resourcePackRepository.setRepositories(repositoryEntries);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/notice/NoticeGui.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.notice;
2 |
3 | import net.minecraft.client.gui.GuiButton;
4 | import net.minecraft.client.gui.GuiScreen;
5 | import net.minecraftforge.fml.relauncher.Side;
6 | import net.minecraftforge.fml.relauncher.SideOnly;
7 | import org.cfpa.i18nupdatemod.I18nConfig;
8 |
9 | import java.awt.*;
10 | import java.io.IOException;
11 | import java.net.URI;
12 | import java.util.List;
13 |
14 | @SideOnly(Side.CLIENT)
15 | public class NoticeGui extends GuiScreen {
16 | private List strings;
17 | private GuiButton noticeGithubButton;
18 | private GuiButton noticeCloseButton;
19 |
20 | public NoticeGui(List strings) {
21 | this.strings = strings;
22 | }
23 |
24 | @Override
25 | public void initGui() {
26 | // 添加按钮
27 | if (I18nConfig.notice.showWeblateButton) {
28 | noticeGithubButton = new GuiButton(0, this.width / 2 - 160, this.height * 75 / 100 + 8, 150, 20, "§l我想要参与模组翻译");
29 | buttonList.add(noticeGithubButton);
30 | }
31 |
32 | noticeCloseButton = new GuiButton(1, this.width / 2 + 8, this.height * 75 / 100 + 8, 150, 20, "§l关闭");
33 | buttonList.add(noticeCloseButton);
34 | }
35 |
36 | @Override
37 | public boolean doesGuiPauseGame() {
38 | return false;
39 | }
40 |
41 | @Override
42 | public void drawScreen(int mouseX, int mouseY, float partialTicks) {
43 | super.drawScreen(mouseX, mouseY, partialTicks);
44 | drawGradientRect((int) (width * 0.1), (int) (height * 0.1), (int) (width * 0.9), (int) (height * 0.75), -0x3fefeff0, -0x2fefeff0);
45 | int h = (int) (this.height * 0.16);
46 | int w = (int) (this.width * 0.14);
47 | StringBuilder sb = new StringBuilder();
48 | strings.forEach(v -> sb.append(v).append('\n'));
49 | fontRenderer.drawSplitString(sb.toString(), w, h, (int) (width * 0.72), 0xffffff);
50 | }
51 |
52 | @Override
53 | protected void actionPerformed(GuiButton button) throws IOException {
54 | super.actionPerformed(button);
55 | if (button == noticeCloseButton) {
56 | mc.displayGuiScreen(null);
57 | return;
58 | }
59 |
60 | if (button == noticeGithubButton) {
61 | String url = "https://cfpa.team";
62 | try {
63 | Desktop.getDesktop().browse(new URI(url));
64 | } catch (Exception e) {
65 | e.printStackTrace();
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/notice/NoticeShower.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.notice;
2 |
3 | import net.minecraft.client.Minecraft;
4 | import net.minecraft.util.text.TextComponentTranslation;
5 | import org.apache.commons.io.IOUtils;
6 | import org.cfpa.i18nupdatemod.I18nUpdateMod;
7 | import org.cfpa.i18nupdatemod.I18nConfig;
8 |
9 | import java.net.URL;
10 | import java.nio.charset.StandardCharsets;
11 | import java.util.List;
12 |
13 | public class NoticeShower {
14 | private static List strings;
15 |
16 | public NoticeShower() {
17 | new Thread(() -> {
18 | try {
19 | URL url = new URL(I18nConfig.notice.noticeURL);
20 | strings = IOUtils.readLines(url.openStream(), StandardCharsets.UTF_8);
21 | Minecraft.getMinecraft().addScheduledTask(() -> Minecraft.getMinecraft().displayGuiScreen(new NoticeGui(strings)));
22 | } catch (Throwable e) {
23 | Minecraft.getMinecraft().player.sendMessage(new TextComponentTranslation("获取公告失败。"));
24 | I18nUpdateMod.logger.error("获取公告失败:", e);
25 | }
26 | }, "I18n_NOTICE_PENDING_THREAD").start();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/notice/ShowNoticeFirst.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.notice;
2 |
3 | import net.minecraftforge.client.event.RenderGameOverlayEvent;
4 | import net.minecraftforge.fml.common.Mod;
5 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
6 | import org.cfpa.i18nupdatemod.I18nUpdateMod;
7 | import org.cfpa.i18nupdatemod.I18nConfig;
8 |
9 | @Mod.EventBusSubscriber(modid = I18nUpdateMod.MODID)
10 | public class ShowNoticeFirst {
11 | public static boolean shouldShowNotice = false;
12 |
13 | @SubscribeEvent
14 | public static void onPlayerFirstJoin(RenderGameOverlayEvent.Post event) {
15 | if (shouldShowNotice && event.getType() != RenderGameOverlayEvent.ElementType.HELMET && I18nConfig.notice.showNoticeConfig) {
16 | shouldShowNotice = false;
17 | new NoticeShower();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/resourcepack/AssetMap.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.resourcepack;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 | import net.minecraft.client.Minecraft;
6 | import org.cfpa.i18nupdatemod.I18nConfig;
7 |
8 | import java.io.*;
9 | import java.util.*;
10 |
11 | import static org.cfpa.i18nupdatemod.I18nUpdateMod.logger;
12 |
13 | public class AssetMap {
14 | private Map> map;
15 |
16 | private static AssetMap INSTANCE;
17 |
18 | public static AssetMap instance() {
19 | if (INSTANCE == null) {
20 | INSTANCE = new AssetMap();
21 | }
22 | return INSTANCE;
23 | }
24 |
25 | private AssetMap() {
26 | // 优先从资源包中读取 json 文件
27 | try {
28 | File f = new File(
29 | Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString() + File.separator +
30 | I18nConfig.download.i18nLangPackName + File.separator +
31 | "assets" + File.separator + "i18nmod" + File.separator + "asset_map" + File.separator + "asset_map.json"
32 | );
33 | this.map = loadJson(f);
34 | if(this.map != null)
35 | return;
36 | } catch (Exception ignore) {
37 | }
38 |
39 | // 加载 jar 包中的 json 文件
40 | ClassLoader classLoader = this.getClass().getClassLoader();
41 | InputStreamReader in = new InputStreamReader(Objects.requireNonNull(classLoader.getResourceAsStream("assets/i18nmod/asset_map/asset_map.json")));
42 | this.map = loadJson(in);
43 | }
44 |
45 | @SuppressWarnings("unchecked")
46 | private Map> loadJson(Reader in) {
47 | Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
48 | return gson.fromJson(in, Map.class);
49 | }
50 |
51 | private Map> loadJson(File f) {
52 | BufferedReader in = null;
53 | Map> jsonMap = null;
54 | try {
55 | in = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
56 | jsonMap = loadJson(in);
57 | in.close();
58 | } catch (Exception e) {
59 | logger.error("error loading json", e);
60 | }
61 | return jsonMap;
62 | }
63 |
64 | public Set getAssetDomains(Set modidSet) {
65 | Set assetDomains = new HashSet<>();
66 | modidSet.stream().map(this::get).forEach(assetDomains::addAll);
67 | assetDomains.addAll(this.get(""));
68 | // 避免遗漏,加入所有未知映射关系的asset
69 | assetDomains.addAll(this.get(""));
70 | return assetDomains;
71 | }
72 |
73 | public Collection get(String modid) {
74 | Collection ret = this.map.get(modid);
75 |
76 | return ret == null ? new HashSet<>() : ret;
77 | }
78 |
79 | public void update(File assetMap) {
80 | Map> jsonMap = loadJson(assetMap);
81 | if(jsonMap != null)
82 | this.map = loadJson(assetMap);
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/org/cfpa/i18nupdatemod/resourcepack/ResourcePackBuilder.java:
--------------------------------------------------------------------------------
1 | package org.cfpa.i18nupdatemod.resourcepack;
2 |
3 | import static org.cfpa.i18nupdatemod.I18nUpdateMod.logger;
4 |
5 | import java.io.BufferedWriter;
6 | import java.io.File;
7 | import java.io.FileOutputStream;
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 | import java.io.OutputStreamWriter;
11 | import java.nio.charset.StandardCharsets;
12 | import java.util.Date;
13 | import java.util.Set;
14 |
15 | import org.cfpa.i18nupdatemod.I18nConfig;
16 | import org.cfpa.i18nupdatemod.I18nUtils;
17 | import org.cfpa.i18nupdatemod.download.DownloadInfoHelper;
18 | import org.cfpa.i18nupdatemod.git.ResourcePackRepository;
19 |
20 | import java.nio.file.Files;
21 | import java.nio.file.StandardCopyOption;
22 | import java.text.SimpleDateFormat;
23 |
24 | import net.minecraft.client.Minecraft;
25 |
26 | public class ResourcePackBuilder {
27 | private File rootPath;
28 | private File assetFolder;
29 | private Set modidSet;
30 |
31 | public ResourcePackBuilder() {
32 | Set modidSet = net.minecraftforge.fml.common.Loader.instance().getIndexedModList().keySet();
33 | rootPath = new File(Minecraft.getMinecraft().getResourcePackRepository().getDirResourcepacks().toString(),
34 | I18nConfig.download.i18nLangPackName);
35 | assetFolder = new File(rootPath, "assets");
36 | this.modidSet = modidSet;
37 | }
38 |
39 | public Set getAssetDomains() {
40 | return AssetMap.instance().getAssetDomains(modidSet);
41 | }
42 |
43 | public boolean checkUpdate() {
44 | // TODO 检查资源包是否合法
45 | if (!(rootPath.exists() || assetFolder.exists())) {
46 | this.copyResourcePack();
47 | return true;
48 | }
49 | // 超过更新检查时间间隔
50 | if (longTimeNoUpdate()) {
51 | return true;
52 | }
53 | // 部分asset文件缺失,可能增加了mod
54 | for (String domain : getAssetDomains()) {
55 | File assetFolder = getAssetFolder(domain);
56 | if (!assetFolder.exists()) {
57 | return true;
58 | }
59 | }
60 | return false;
61 | }
62 |
63 | private File getAssetFolder(String domain) {
64 | return new File(assetFolder, domain);
65 | }
66 |
67 | private boolean longTimeNoUpdate() {
68 | File f = new File(rootPath, "pack.mcmeta");
69 | try {
70 | return (System.currentTimeMillis() - f.lastModified()) > (I18nConfig.download.maxDay * 24 * 3600 * 1000);
71 | } catch (Throwable e) {
72 | logger.error("检查文件日期失败", e);
73 | return true;
74 | }
75 | }
76 |
77 | private void copyResourcePack() {
78 | assetFolder.mkdirs();
79 | // PNG 图标
80 | File icon = new File(rootPath, "pack.png");
81 | if (!icon.exists()) {
82 | ClassLoader classLoader = this.getClass().getClassLoader();
83 | InputStream in = classLoader.getResourceAsStream("assets/i18nmod/icon/pack.png");
84 |
85 | try {
86 | Files.copy(in, icon.toPath(), StandardCopyOption.REPLACE_EXISTING);
87 | } catch (IOException e) {
88 | logger.error("Error while copying icon file:", e);
89 | }
90 | }
91 | // pack.mcmeta
92 | writePackMeta();
93 | }
94 |
95 | private void writePackMeta() {
96 | SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
97 | String dateTime = df.format(new Date());
98 | // 这里给pack.mcmeta加了行注释,测试没问题,但理论上如果是json文件不能加注释
99 | dateTime = "# 修改时间:" + dateTime;
100 | File info = new File(rootPath, "pack.mcmeta");
101 | String meta = "{\n" + " \"pack\": {\n" + " \"pack_format\": 3,\n"
102 | + " \"description\": \"I18n Update Mod 汉化包\"\n" + " }\n" + "}\n";
103 | try {
104 | BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(info), StandardCharsets.UTF_8));
105 | writer.write(dateTime + "\n" + meta);
106 | writer.flush();
107 | writer.close();
108 | } catch (IOException e) {
109 | logger.error("Error while trying to write pack.mcmeta: ", e);
110 | }
111 | }
112 |
113 | public void touch() {
114 | // 写pack.mcmeta文件,作为更新时间标记
115 | writePackMeta();
116 | }
117 |
118 | public void updateAllNeededFilesFromRepo(ResourcePackRepository repo) {
119 | // TODO 只复制需要更新的文件,可以考虑给copyDir方法加filter
120 | for (String domain : this.getAssetDomains()) {
121 | try {
122 | copyAssetsFromRepo(domain, repo);
123 | } catch (IOException e) {
124 | logger.error("Error while updating language file: ", e);
125 | DownloadInfoHelper.info.add("模组 " + domain + " 的语言文件加载失败,请考虑反馈此问题。");
126 | }
127 | }
128 | }
129 |
130 | private void copyAssetsFromRepo(String domain, ResourcePackRepository repo) throws IOException {
131 | File from = new File(repo.getLocalPath(), ResourcePackRepository.getSubPathOfAsset(domain));
132 | File to = new File(this.assetFolder, domain);
133 | if (from.exists()) {
134 | I18nUtils.copyDir(from.toPath(), to.toPath());
135 | } else {
136 | to.mkdirs();
137 | }
138 | }
139 |
140 | }
141 |
--------------------------------------------------------------------------------
/src/main/resources/LICENSE-JGit:
--------------------------------------------------------------------------------
1 | This program and the accompanying materials are made available
2 | under the terms of the Eclipse Distribution License v1.0 which
3 | accompanies this distribution, is reproduced below, and is
4 | available at http://www.eclipse.org/org/documents/edl-v10.php
5 |
6 | All rights reserved.
7 |
8 | Redistribution and use in source and binary forms, with or
9 | without modification, are permitted provided that the following
10 | conditions are met:
11 |
12 | - Redistributions of source code must retain the above copyright
13 | notice, this list of conditions and the following disclaimer.
14 |
15 | - Redistributions in binary form must reproduce the above
16 | copyright notice, this list of conditions and the following
17 | disclaimer in the documentation and/or other materials provided
18 | with the distribution.
19 |
20 | - Neither the name of the Eclipse Foundation, Inc. nor the
21 | names of its contributors may be used to endorse or promote
22 | products derived from this software without specific prior
23 | written permission.
24 |
25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
26 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
27 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
30 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
32 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/src/main/resources/META-INF/i18nmod_at.cfg:
--------------------------------------------------------------------------------
1 | public net.minecraft.client.resources.LanguageManager field_135048_c # currentLanguage
--------------------------------------------------------------------------------
/src/main/resources/assets/i18nmod/asset_map/asset_map.json:
--------------------------------------------------------------------------------
1 | {
2 | "1_13_features": [
3 | "1_13_features"
4 | ],
5 | "": [
6 | "0x_trans_fix",
7 | "forge",
8 | "i18nmod",
9 | "liteloader",
10 | "minecraft"
11 | ],
12 | "": [
13 | "elulib",
14 | "presets",
15 | "solarfluxreborn",
16 | "thaumicreadoption"
17 | ],
18 | "": [
19 | "astikoor",
20 | "catsdogs",
21 | "clockworkphase"
22 | ],
23 | "InfernalMobs": [
24 | "infernalmobs"
25 | ],
26 | "OpenEye": [
27 | "openeye"
28 | ],
29 | "VoltzEngine": [
30 | "voltzengine"
31 | ],
32 | "abyssalcraft": [
33 | "abyssalcraft"
34 | ],
35 | "acheads": [
36 | "acheads"
37 | ],
38 | "acintegration": [
39 | "acintegration"
40 | ],
41 | "actuallyadditions": [
42 | "actuallyadditions"
43 | ],
44 | "adchimneys": [
45 | "adchimneys"
46 | ],
47 | "additionalbanners": [
48 | "additionalbanners"
49 | ],
50 | "additionalcompression": [
51 | "additionalcompression"
52 | ],
53 | "adhooks": [
54 | "adhooks"
55 | ],
56 | "adpoles": [
57 | "adpoles"
58 | ],
59 | "adpother": [
60 | "adpother"
61 | ],
62 | "advanced_machines": [
63 | "advanced_machines"
64 | ],
65 | "advanced_solar_panels": [
66 | "advanced_solar_panels"
67 | ],
68 | "advancedcombat": [
69 | "advancedcombat"
70 | ],
71 | "advancedmortars": [
72 | "advancedmortars"
73 | ],
74 | "advancedrocketry": [
75 | "advancedrocketry"
76 | ],
77 | "advancementbook": [
78 | "advancementbook"
79 | ],
80 | "advgenerators": [
81 | "advgenerators"
82 | ],
83 | "ae2stuff": [
84 | "ae2stuff"
85 | ],
86 | "ae2wtlib": [
87 | "ae2wtlib"
88 | ],
89 | "aenetvistool": [
90 | "aenetvistool"
91 | ],
92 | "aether_legacy": [
93 | "aether_legacy"
94 | ],
95 | "aether_legacy_addon": [
96 | "aether_legacy_addon"
97 | ],
98 | "aetherworks": [
99 | "aetherworks"
100 | ],
101 | "agricraft": [
102 | "agricraft"
103 | ],
104 | "akashictome": [
105 | "akashictome"
106 | ],
107 | "alchemistry": [
108 | "alchemistry"
109 | ],
110 | "alternatingflux": [
111 | "alternatingflux"
112 | ],
113 | "ambidextrous": [
114 | "ambidextrous"
115 | ],
116 | "ancientwarfare": [
117 | "ancientwarfare"
118 | ],
119 | "ancientwarfareautomation": [
120 | "ancientwarfare"
121 | ],
122 | "ancientwarfarenpc": [
123 | "ancientwarfare"
124 | ],
125 | "ancientwarfarestructure": [
126 | "ancientwarfare"
127 | ],
128 | "ancientwarfarevehicle": [
129 | "ancientwarfare"
130 | ],
131 | "angermanagement": [
132 | "angermanagement"
133 | ],
134 | "animalbikes": [
135 | "animalbikes"
136 | ],
137 | "animalcrops": [
138 | "animalcrops"
139 | ],
140 | "animalium": [
141 | "animalium"
142 | ],
143 | "animania": [
144 | "animania"
145 | ],
146 | "animus": [
147 | "animus"
148 | ],
149 | "antiqueatlas": [
150 | "antiqueatlas"
151 | ],
152 | "antiqueatlasoverlay": [
153 | "antiqueatlas"
154 | ],
155 | "aov": [
156 | "aov"
157 | ],
158 | "apotheosis": [
159 | "apotheosis"
160 | ],
161 | "applecore": [
162 | "applecore"
163 | ],
164 | "appliedenergistics2": [
165 | "appliedenergistics2"
166 | ],
167 | "aquaculture": [
168 | "aquaculture"
169 | ],
170 | "arcademod": [
171 | "arcademod"
172 | ],
173 | "arcaneworld": [
174 | "arcaneworld"
175 | ],
176 | "architecturecraft": [
177 | "architecturecraft"
178 | ],
179 | "armorplus": [
180 | "armorplus"
181 | ],
182 | "armorunder": [
183 | "vanillafoodpantry"
184 | ],
185 | "armoryexpansion": [
186 | "armoryexpansion"
187 | ],
188 | "armoryexpansion-conarm": [
189 | "armoryexpansion"
190 | ],
191 | "armoryexpansion-custommaterials": [
192 | "armoryexpansion"
193 | ],
194 | "armoryexpansion-iceandfire": [
195 | "armoryexpansion"
196 | ],
197 | "armoryexpansion-matteroverdrive": [
198 | "armoryexpansion"
199 | ],
200 | "armourers_workshop": [
201 | "armourers_workshop"
202 | ],
203 | "aroma1997core": [
204 | "aroma1997core"
205 | ],
206 | "aroma1997sdimension": [
207 | "aroma1997sdimension"
208 | ],
209 | "aromabackup": [
210 | "aromabackup",
211 | "aromabackuprecovery"
212 | ],
213 | "aromabackuprecovery": [
214 | "aromabackup",
215 | "aromabackuprecovery"
216 | ],
217 | "artemislib": [
218 | "artemislib"
219 | ],
220 | "artisanworktables": [
221 | "artisanworktables"
222 | ],
223 | "asmodeuscore": [
224 | "asmodeuscore"
225 | ],
226 | "astikorcarts": [
227 | "astikorcarts"
228 | ],
229 | "astralsorcery": [
230 | "astralsorcery"
231 | ],
232 | "athenaeum": [
233 | "athenaeum"
234 | ],
235 | "atlcraft": [
236 | "atlcraft"
237 | ],
238 | "atmosmobs": [
239 | "primitivemobs"
240 | ],
241 | "atmtweaks": [
242 | "atmtweaks"
243 | ],
244 | "auraddons": [
245 | "auraddons"
246 | ],
247 | "autopackager": [
248 | "autopackager"
249 | ],
250 | "autoreglib": [
251 | "autoreglib"
252 | ],
253 | "autoverse": [
254 | "autoverse"
255 | ],
256 | "avaritia": [
257 | "avaritia"
258 | ],
259 | "avaritiatweaks": [
260 | "avaritiatweaks"
261 | ],
262 | "babymobs": [
263 | "babymobs"
264 | ],
265 | "backpack": [
266 | "backpack"
267 | ],
268 | "backpacks16840": [
269 | "backpacks16840",
270 | "backpacks16840/lang"
271 | ],
272 | "badwithernocookiereloaded": [
273 | "badwithernocookiereloaded"
274 | ],
275 | "bagelsmore": [
276 | "bagelsmore"
277 | ],
278 | "bakerscraft": [
279 | "bakerscraft"
280 | ],
281 | "balancedclaytools": [
282 | "balancedclaytools"
283 | ],
284 | "base": [
285 | "base"
286 | ],
287 | "basemetals": [
288 | "basemetals"
289 | ],
290 | "battletowers": [
291 | "battletowers"
292 | ],
293 | "baublelicious": [
294 | "baublelicious"
295 | ],
296 | "baubles": [
297 | "baubles"
298 | ],
299 | "bdlib": [
300 | "bdlib"
301 | ],
302 | "bdsandm": [
303 | "bdsandm"
304 | ],
305 | "bedbugs": [
306 | "bedbugs"
307 | ],
308 | "bedrockbgone": [
309 | "bedrockbgone"
310 | ],
311 | "beebetteratbees": [
312 | "beebetteratbees"
313 | ],
314 | "beneath": [
315 | "beneath"
316 | ],
317 | "better_diving": [
318 | "better_diving"
319 | ],
320 | "betteragriculture": [
321 | "betteragriculture"
322 | ],
323 | "betterbuilderswands": [
324 | "betterbuilderswands"
325 | ],
326 | "betterfarmland": [
327 | "betterfarmland"
328 | ],
329 | "betterfoliage": [
330 | "betterfoliage"
331 | ],
332 | "betterfps": [
333 | "betterfps"
334 | ],
335 | "bettermobgriefinggamerule": [
336 | "bettermobgriefinggamerule"
337 | ],
338 | "betternether": [
339 | "betternether"
340 | ],
341 | "betterquesting": [
342 | "betterquesting"
343 | ],
344 | "betterrecords": [
345 | "betterrecords"
346 | ],
347 | "bettersprinting": [
348 | "bettersprinting"
349 | ],
350 | "bettertitlescreen": [
351 | "bettertitlescreen"
352 | ],
353 | "betterwithaddons": [
354 | "betterwithaddons"
355 | ],
356 | "betterwithengineering": [
357 | "betterwithengineering"
358 | ],
359 | "betterwithhardersteelrecipe": [
360 | "betterwithhardersteelrecipe"
361 | ],
362 | "betterwithmods": [
363 | "betterwithmods"
364 | ],
365 | "bhc": [
366 | "bhc"
367 | ],
368 | "bibliocraft": [
369 | "bibliocraft"
370 | ],
371 | "bibliowoodsbop": [
372 | "bibliocraft"
373 | ],
374 | "bibliowoodsbotania": [
375 | "bibliocraft"
376 | ],
377 | "bibliowoodsforestry": [
378 | "bibliocraft"
379 | ],
380 | "bibliowoodsnatura": [
381 | "bibliocraft"
382 | ],
383 | "bigreactors": [
384 | "bigreactors"
385 | ],
386 | "binniecore": [
387 | "binniecore",
388 | "binniedesign",
389 | "botany",
390 | "extrabees",
391 | "extratrees",
392 | "genetics"
393 | ],
394 | "binniedesign": [
395 | "binniecore",
396 | "binniedesign",
397 | "botany",
398 | "extrabees",
399 | "extratrees",
400 | "genetics"
401 | ],
402 | "biomesoplenty": [
403 | "biomesoplenty"
404 | ],
405 | "biometweaker": [
406 | "biometweaker"
407 | ],
408 | "bionisation3": [
409 | "bionisation3"
410 | ],
411 | "birdsfoods": [
412 | "birdsfoods"
413 | ],
414 | "birdsnests": [
415 | "birdsnests"
416 | ],
417 | "bitcoin": [
418 | "bitcoin"
419 | ],
420 | "blackholestorage": [
421 | "blackholestorage"
422 | ],
423 | "blockarmor": [
424 | "blockarmor"
425 | ],
426 | "blockcraftery": [
427 | "blockcraftery"
428 | ],
429 | "blocklayering": [
430 | "blocklayering"
431 | ],
432 | "bloodarsenal": [
433 | "bloodarsenal",
434 | "bloodarsenalguide"
435 | ],
436 | "bloodmagic": [
437 | "bloodmagic",
438 | "bloodmagic/lan",
439 | "bloodmagicguide"
440 | ],
441 | "bloodmoon": [
442 | "bloodmoon"
443 | ],
444 | "blur": [
445 | "blur"
446 | ],
447 | "bnbgaminglib": [
448 | "bnbgaminglib"
449 | ],
450 | "boneappetit": [
451 | "boneappetit"
452 | ],
453 | "bonsaitrees": [
454 | "bonsaitrees"
455 | ],
456 | "bookshelf": [
457 | "bookshelf"
458 | ],
459 | "botania": [
460 | "botania"
461 | ],
462 | "botania_tweaks": [
463 | "botania_tweaks"
464 | ],
465 | "botanicadds": [
466 | "botanicadds"
467 | ],
468 | "botanicbonsai": [
469 | "botanicbonsai"
470 | ],
471 | "botany": [
472 | "binniecore",
473 | "binniedesign",
474 | "botany",
475 | "extrabees",
476 | "extratrees",
477 | "genetics"
478 | ],
479 | "botaunomy": [
480 | "botaunomy"
481 | ],
482 | "bouncy_creepers": [
483 | "bouncy_creepers"
484 | ],
485 | "bq_standard": [
486 | "bq_standard"
487 | ],
488 | "bqt": [
489 | "bqt"
490 | ],
491 | "brandonscore": [
492 | "brandonscore"
493 | ],
494 | "brokenwings": [
495 | "brokenwings"
496 | ],
497 | "btg": [
498 | "btg"
499 | ],
500 | "buildcraftbuilders": [
501 | "buildcraft",
502 | "buildcraftcompat"
503 | ],
504 | "buildcraftcompat": [
505 | "buildcraftcompat-buildcraft-compat"
506 | ],
507 | "buildcraftcore": [
508 | "buildcraft",
509 | "buildcraft-buildcraft-core",
510 | "buildcraftcompat"
511 | ],
512 | "buildcraftenergy": [
513 | "buildcraft",
514 | "buildcraftcompat"
515 | ],
516 | "buildcraftfactory": [
517 | "buildcraft",
518 | "buildcraftcompat"
519 | ],
520 | "buildcraftlib": [
521 | "buildcraft",
522 | "buildcraftcompat"
523 | ],
524 | "buildcraftrobotics": [
525 | "buildcraft",
526 | "buildcraftcompat"
527 | ],
528 | "buildcraftsilicon": [
529 | "buildcraft",
530 | "buildcraftcompat"
531 | ],
532 | "buildcrafttransport": [
533 | "buildcraft",
534 | "buildcraftcompat"
535 | ],
536 | "buildinggadgets": [
537 | "buildinggadgets"
538 | ],
539 | "bullseye": [
540 | "bullseye"
541 | ],
542 | "calculator": [
543 | "calculator"
544 | ],
545 | "cameraobscura": [
546 | "cameraobscura"
547 | ],
548 | "camping": [
549 | "camping"
550 | ],
551 | "capabilityproxy": [
552 | "capabilityproxy"
553 | ],
554 | "car": [
555 | "car"
556 | ],
557 | "careerbees": [
558 | "careerbees"
559 | ],
560 | "carrots": [
561 | "vanillafoodpantry"
562 | ],
563 | "carryon": [
564 | "carryon"
565 | ],
566 | "cavern": [
567 | "cavern"
568 | ],
569 | "cctweaked": [
570 | "computercraft"
571 | ],
572 | "cd4017be_lib": [
573 | "cd4017be_lib"
574 | ],
575 | "cdm": [
576 | "cdm"
577 | ],
578 | "ceramics": [
579 | "ceramics"
580 | ],
581 | "ceu": [
582 | "ceu"
583 | ],
584 | "cfm": [
585 | "cfm"
586 | ],
587 | "cgm": [
588 | "cgm"
589 | ],
590 | "champions": [
591 | "champions"
592 | ],
593 | "chancecubes": [
594 | "chancecubes"
595 | ],
596 | "charcoal_pit": [
597 | "charcoal_pit"
598 | ],
599 | "charcoalblock": [
600 | "charcoalblock"
601 | ],
602 | "chargers": [
603 | "chargers"
604 | ],
605 | "charset": [
606 | "charset"
607 | ],
608 | "chatoverhaul": [
609 | "chatoverhaul"
610 | ],
611 | "chattweaks": [
612 | "chattweaks"
613 | ],
614 | "chesttransporter": [
615 | "chesttransporter"
616 | ],
617 | "chickenchunks": [
618 | "chickenchunks"
619 | ],
620 | "chickens": [
621 | "chickens"
622 | ],
623 | "chinjufumod": [
624 | "chinjufumod"
625 | ],
626 | "chisel": [
627 | "chisel",
628 | "chisel_guide"
629 | ],
630 | "chiselsandbits": [
631 | "chiselsandbits"
632 | ],
633 | "chococraft": [
634 | "chococraft"
635 | ],
636 | "circuits": [
637 | "circuits"
638 | ],
639 | "cjcm": [
640 | "cjcm"
641 | ],
642 | "claybucket": [
643 | "claybucket"
644 | ],
645 | "cleanview": [
646 | "cleanview"
647 | ],
648 | "clickmachine": [
649 | "clickmachine"
650 | ],
651 | "clienttweaks": [
652 | "clienttweaks"
653 | ],
654 | "clipboard": [
655 | "clipboard"
656 | ],
657 | "cofhcore": [
658 | "cofh"
659 | ],
660 | "cofhworld": [
661 | "cofhworld"
662 | ],
663 | "colored_water": [
664 | "colored_water"
665 | ],
666 | "colossalchests": [
667 | "colossalchests"
668 | ],
669 | "colytra": [
670 | "colytra"
671 | ],
672 | "combustfish": [
673 | "combustfish"
674 | ],
675 | "comforts": [
676 | "comforts"
677 | ],
678 | "commoncapabilities": [
679 | "commoncapabilities"
680 | ],
681 | "communism": [
682 | "communism"
683 | ],
684 | "compacter": [
685 | "compacter"
686 | ],
687 | "compactmachines3": [
688 | "compactmachines3"
689 | ],
690 | "compactsolars": [
691 | "compactsolars"
692 | ],
693 | "compactstorage": [
694 | "compactstorage"
695 | ],
696 | "compatskills": [
697 | "compatskills"
698 | ],
699 | "compositegear": [
700 | "compositegear"
701 | ],
702 | "computercraft": [
703 | "computercraft",
704 | "computercraft-computercraft"
705 | ],
706 | "conarm": [
707 | "conarm"
708 | ],
709 | "concrete": [
710 | "concrete"
711 | ],
712 | "configmod": [
713 | "coroutil"
714 | ],
715 | "controlling": [
716 | "controlling"
717 | ],
718 | "cookingforblockheads": [
719 | "cookingforblockheads"
720 | ],
721 | "corail_pillar": [
722 | "corail_pillar"
723 | ],
724 | "coroutil": [
725 | "coroutil"
726 | ],
727 | "corpsecomplex": [
728 | "corpsecomplex"
729 | ],
730 | "cosmeticarmorreworked": [
731 | "cosmeticarmorreworked"
732 | ],
733 | "craftingtweaks": [
734 | "craftingtweaks"
735 | ],
736 | "creativecore": [
737 | "creativecore"
738 | ],
739 | "creativefirework": [
740 | "creativefirework"
741 | ],
742 | "creeperconfetti": [
743 | "creeperconfetti"
744 | ],
745 | "croparia": [
746 | "croparia"
747 | ],
748 | "cryingobsidian": [
749 | "cryingobsidian"
750 | ],
751 | "csb_ench_table": [
752 | "csb_ench_table"
753 | ],
754 | "cubicvillager": [
755 | "cubicvillager"
756 | ],
757 | "cucumber": [
758 | "cucumber"
759 | ],
760 | "culinaryconstruct": [
761 | "culinaryconstruct"
762 | ],
763 | "customnpcs": [
764 | "customnpcs",
765 | "moreplayermodels"
766 | ],
767 | "cyberware": [
768 | "cyberware"
769 | ],
770 | "cyclicmagic": [
771 | "cyclicmagic"
772 | ],
773 | "cyclopscore": [
774 | "cyclopscore"
775 | ],
776 | "d3core": [
777 | "d3core"
778 | ],
779 | "dailies": [
780 | "dailiesmod"
781 | ],
782 | "danknull": [
783 | "danknull"
784 | ],
785 | "darkutils": [
786 | "darkutils"
787 | ],
788 | "davincisvessels": [
789 | "davincisvessels"
790 | ],
791 | "death_compass": [
792 | "death_compass"
793 | ],
794 | "deconstruction": [
795 | "deconstruction"
796 | ],
797 | "deepmoblearning": [
798 | "deepmoblearning"
799 | ],
800 | "deepmoblearningbm": [
801 | "deepmoblearningbm"
802 | ],
803 | "deepresonance": [
804 | "deepresonance"
805 | ],
806 | "defaultworldgenerator-port": [
807 | "defaultworldgenerator-port"
808 | ],
809 | "defiledlands": [
810 | "defiledlands"
811 | ],
812 | "demagnetize": [
813 | "demagnetize"
814 | ],
815 | "df-roads": [
816 | "df-roads"
817 | ],
818 | "diamondlamps": [
819 | "diamondlamps"
820 | ],
821 | "dimensionalcontrol": [
822 | "dimensionalcontrol"
823 | ],
824 | "dimensionaledibles": [
825 | "dimensionaledibles"
826 | ],
827 | "dimstages": [
828 | "dimstages"
829 | ],
830 | "disenchanter": [
831 | "disenchanter"
832 | ],
833 | "dismantler": [
834 | "dismantler"
835 | ],
836 | "dmonsters": [
837 | "dmonsters"
838 | ],
839 | "dmp": [
840 | "dmp"
841 | ],
842 | "doggytalents": [
843 | "doggytalents"
844 | ],
845 | "draconicevolution": [
846 | "draconicevolution"
847 | ],
848 | "dragonmounts": [
849 | "dragonmounts"
850 | ],
851 | "drones": [
852 | "drones"
853 | ],
854 | "dsurround": [
855 | "dsurround"
856 | ],
857 | "duckymod": [
858 | "duckymod"
859 | ],
860 | "dungeontactics": [
861 | "dungeontactics"
862 | ],
863 | "dungpipe": [
864 | "dungpipe"
865 | ],
866 | "durabilityshow": [
867 | "durabilityshow"
868 | ],
869 | "durabilityviewer": [
870 | "durabilityviewer"
871 | ],
872 | "dynamictrees": [
873 | "dynamictrees"
874 | ],
875 | "dynamictreesbop": [
876 | "dynamictreesbop"
877 | ],
878 | "dynamictreesphc": [
879 | "dynamictreesphc"
880 | ],
881 | "earthworks": [
882 | "earthworks"
883 | ],
884 | "ebwizardry": [
885 | "ebwizardry"
886 | ],
887 | "ediblebugs": [
888 | "ediblebugs"
889 | ],
890 | "efab": [
891 | "efab"
892 | ],
893 | "effortlessbuilding": [
894 | "effortlessbuilding"
895 | ],
896 | "eiramoticons": [
897 | "eiramoticons"
898 | ],
899 | "eleccore": [
900 | "eleccore"
901 | ],
902 | "eleccoreloader": [
903 | "eleccore"
904 | ],
905 | "electrostatics": [
906 | "electrostatics"
907 | ],
908 | "elevatorid": [
909 | "elevatorid"
910 | ],
911 | "emberroot": [
912 | "emberroot"
913 | ],
914 | "embers": [
915 | "embers"
916 | ],
917 | "emcbuilderswand": [
918 | "emcbuilderswand"
919 | ],
920 | "emojiful": [
921 | "emojiful"
922 | ],
923 | "enchanting_tweaks": [
924 | "enchanting_tweaks"
925 | ],
926 | "enchdesc": [
927 | "enchdesc"
928 | ],
929 | "enchiridion": [
930 | "enchiridion"
931 | ],
932 | "endercompass": [
933 | "endercompass"
934 | ],
935 | "endercore": [
936 | "endercore"
937 | ],
938 | "endercrop": [
939 | "endercrop"
940 | ],
941 | "enderio": [
942 | "enderio",
943 | "enderio-ender-io-base"
944 | ],
945 | "enderiobase": [
946 | "enderio",
947 | "enderio-ender-io-base"
948 | ],
949 | "enderioconduits": [
950 | "enderio",
951 | "enderio-ender-io-conduits"
952 | ],
953 | "enderioconduitsappliedenergistics": [
954 | "enderio",
955 | "enderio-ender-io-ae2-conduits"
956 | ],
957 | "enderioconduitsopencomputers": [
958 | "enderio",
959 | "enderio-ender-io-oc-conduits"
960 | ],
961 | "enderioconduitsrefinedstorage": [
962 | "enderio",
963 | "enderio-ender-io-rs-conduits"
964 | ],
965 | "enderioendergy": [
966 | "enderio-ender-io-endergy"
967 | ],
968 | "enderiointegrationforestry": [
969 | "enderio",
970 | "enderio-ender-io-forestry"
971 | ],
972 | "enderiointegrationtic": [
973 | "enderio",
974 | "enderio-ender-io-tic"
975 | ],
976 | "enderiointegrationticlate": [
977 | "enderio",
978 | "enderio-ender-io-tic"
979 | ],
980 | "enderiomachines": [
981 | "enderio",
982 | "enderio-ender-io-machines"
983 | ],
984 | "enderiopowertools": [
985 | "enderio",
986 | "enderio-ender-io-conduits"
987 | ],
988 | "enderiozoo": [
989 | "enderio-ender-io-zoo"
990 | ],
991 | "endermanevo": [
992 | "endermanevo"
993 | ],
994 | "enderpay": [
995 | "enderpay"
996 | ],
997 | "enderrift": [
998 | "enderrift"
999 | ],
1000 | "enderstorage": [
1001 | "enderstorage"
1002 | ],
1003 | "endertanks": [
1004 | "endertanks"
1005 | ],
1006 | "enderutilities": [
1007 | "enderutilities"
1008 | ],
1009 | "enderzoo": [
1010 | "enderzoo"
1011 | ],
1012 | "endmetals": [
1013 | "endmetals"
1014 | ],
1015 | "energeticsheep": [
1016 | "energeticsheep"
1017 | ],
1018 | "energyconverters": [
1019 | "energyconverters"
1020 | ],
1021 | "energysynergy": [
1022 | "energysynergy"
1023 | ],
1024 | "engineersdoors": [
1025 | "engineersdoors"
1026 | ],
1027 | "engineersworkshop": [
1028 | "engineersworkshop",
1029 | "engineersworkshop-engineers-workshop-reborn"
1030 | ],
1031 | "enhancedarmaments": [
1032 | "enhancedarmaments"
1033 | ],
1034 | "environmentalmaterials": [
1035 | "environmentalmaterials"
1036 | ],
1037 | "environmentaltech": [
1038 | "environmentaltech"
1039 | ],
1040 | "eplus": [
1041 | "eplus"
1042 | ],
1043 | "equipmenttooltips": [
1044 | "equipmenttooltips"
1045 | ],
1046 | "equivadditions": [
1047 | "equivadditions"
1048 | ],
1049 | "equivalentenergistics": [
1050 | "equivalentenergistics"
1051 | ],
1052 | "equivalentintegrations": [
1053 | "equivalentintegrations"
1054 | ],
1055 | "erebus": [
1056 | "erebus"
1057 | ],
1058 | "eternalsingularity": [
1059 | "eternalsingularity"
1060 | ],
1061 | "etlunar": [
1062 | "etlunar"
1063 | ],
1064 | "everlastingabilities": [
1065 | "everlastingabilities"
1066 | ],
1067 | "evilcraftcompat": [
1068 | "evilcraft",
1069 | "evilcraftcompat"
1070 | ],
1071 | "examplemod": [
1072 | "voxelmap"
1073 | ],
1074 | "exchangers": [
1075 | "exchangers"
1076 | ],
1077 | "excompressum": [
1078 | "excompressum"
1079 | ],
1080 | "exnihilocreatio": [
1081 | "exnihilocreatio"
1082 | ],
1083 | "exoticbirds": [
1084 | "exoticbirds"
1085 | ],
1086 | "expindustry": [
1087 | "expindustry"
1088 | ],
1089 | "extendedcrafting": [
1090 | "extendedcrafting"
1091 | ],
1092 | "extendedrenderer": [
1093 | "coroutil"
1094 | ],
1095 | "extraalchemy": [
1096 | "extraalchemy"
1097 | ],
1098 | "extraambiance": [
1099 | "extraambiance"
1100 | ],
1101 | "extrabees": [
1102 | "binniecore",
1103 | "binniedesign",
1104 | "botany",
1105 | "extrabees",
1106 | "extratrees",
1107 | "genetics"
1108 | ],
1109 | "extrabitmanipulation": [
1110 | "extrabitmanipulation"
1111 | ],
1112 | "extracells": [
1113 | "extracells"
1114 | ],
1115 | "extrarails": [
1116 | "extrarails"
1117 | ],
1118 | "extratrees": [
1119 | "binniecore",
1120 | "binniedesign",
1121 | "botany",
1122 | "extrabees",
1123 | "extratrees",
1124 | "genetics"
1125 | ],
1126 | "extrautils2": [
1127 | "extrautils2"
1128 | ],
1129 | "ezstorage": [
1130 | "ezstorage"
1131 | ],
1132 | "ezwastelands": [
1133 | "ezwastelands"
1134 | ],
1135 | "f0-resources": [
1136 | "f0-resources"
1137 | ],
1138 | "factorytech": [
1139 | "factorytech"
1140 | ],
1141 | "fairylights": [
1142 | "fairylights"
1143 | ],
1144 | "fancylamps": [
1145 | "fancylamps"
1146 | ],
1147 | "farmingforblockheads": [
1148 | "farmingforblockheads"
1149 | ],
1150 | "fat_cat": [
1151 | "fat_cat"
1152 | ],
1153 | "fenceoverhaul": [
1154 | "fenceoverhaul"
1155 | ],
1156 | "ferdinandsflowers": [
1157 | "ferdinandsflowers"
1158 | ],
1159 | "ffs": [
1160 | "ffs"
1161 | ],
1162 | "findme": [
1163 | "findme"
1164 | ],
1165 | "flammpfeil.slashblade": [
1166 | "flammpfeil.slashblade"
1167 | ],
1168 | "flansmod": [
1169 | "flansmod"
1170 | ],
1171 | "flatcoloredblocks": [
1172 | "flatcoloredblocks"
1173 | ],
1174 | "flexibletools": [
1175 | "flexibletools"
1176 | ],
1177 | "flightboost": [
1178 | "flightboost"
1179 | ],
1180 | "floatingrails": [
1181 | "floatingrails"
1182 | ],
1183 | "floodlights": [
1184 | "floodlights"
1185 | ],
1186 | "floralchemy": [
1187 | "floralchemy"
1188 | ],
1189 | "fluidconverters": [
1190 | "fluidconverters"
1191 | ],
1192 | "fluidcows": [
1193 | "fluidcows"
1194 | ],
1195 | "fluidfunnel": [
1196 | "fluidfunnel"
1197 | ],
1198 | "fluidtank": [
1199 | "fluidtank"
1200 | ],
1201 | "fluidtank_ae2": [
1202 | "fluidtank"
1203 | ],
1204 | "fluidtank_theoneprobe": [
1205 | "fluidtank"
1206 | ],
1207 | "fluxnetworks": [
1208 | "fluxnetworks"
1209 | ],
1210 | "flyringbaublemod": [
1211 | "flyringbaublemod"
1212 | ],
1213 | "foamflower": [
1214 | "foamflower"
1215 | ],
1216 | "foodexpansion": [
1217 | "foodexpansion"
1218 | ],
1219 | "forbidden_arcanus": [
1220 | "forbidden_arcanus"
1221 | ],
1222 | "forgecraft": [
1223 | "forgecraft"
1224 | ],
1225 | "forgemultipartcbe": [
1226 | "multipart"
1227 | ],
1228 | "fossil": [
1229 | "fossil"
1230 | ],
1231 | "foundry": [
1232 | "foundry"
1233 | ],
1234 | "fp": [
1235 | "fp"
1236 | ],
1237 | "fp.api": [
1238 | "fp"
1239 | ],
1240 | "fpsreducer": [
1241 | "fpsreducer"
1242 | ],
1243 | "framland": [
1244 | "framland"
1245 | ],
1246 | "fsmm": [
1247 | "fsmm"
1248 | ],
1249 | "ftb_beast_coin_miner": [
1250 | "ftb_beast_coin_miner"
1251 | ],
1252 | "ftb_odyssey": [
1253 | "ftb_odyssey"
1254 | ],
1255 | "ftbachievements": [
1256 | "ftbachievements"
1257 | ],
1258 | "ftbbackups": [
1259 | "ftbbackups"
1260 | ],
1261 | "ftbbanners": [
1262 | "ftbbanners"
1263 | ],
1264 | "ftbeggs": [
1265 | "ftbeggs"
1266 | ],
1267 | "ftbeggsconvention": [
1268 | "ftbeggs"
1269 | ],
1270 | "ftbguides": [
1271 | "ftbguides",
1272 | "ftbguides-ftb-guides-2"
1273 | ],
1274 | "ftblib": [
1275 | "ftblib"
1276 | ],
1277 | "ftbmoney": [
1278 | "ftbmoney"
1279 | ],
1280 | "ftbquests": [
1281 | "ftbquests"
1282 | ],
1283 | "ftbutilities": [
1284 | "ftbutilities"
1285 | ],
1286 | "ftgumod": [
1287 | "ftgumod"
1288 | ],
1289 | "funkylocomotion": [
1290 | "funkylocomotion"
1291 | ],
1292 | "furnaceoverhaul": [
1293 | "furnaceoverhaul"
1294 | ],
1295 | "furnus": [
1296 | "furnus"
1297 | ],
1298 | "fw": [
1299 | "fw"
1300 | ],
1301 | "galaxyspace": [
1302 | "galaxyspace"
1303 | ],
1304 | "gamestages": [
1305 | "gamestages"
1306 | ],
1307 | "gardenstuff": [
1308 | "gardenstuff"
1309 | ],
1310 | "gasconduits": [
1311 | "gasconduits"
1312 | ],
1313 | "gbook": [
1314 | "gbook"
1315 | ],
1316 | "gearswap": [
1317 | "gearswap"
1318 | ],
1319 | "gendustry": [
1320 | "gendustry"
1321 | ],
1322 | "genetics": [
1323 | "binniecore",
1324 | "binniedesign",
1325 | "botany",
1326 | "extrabees",
1327 | "extratrees",
1328 | "genetics"
1329 | ],
1330 | "geolosys": [
1331 | "geolosys"
1332 | ],
1333 | "globalgamerules": [
1334 | "globalgamerules"
1335 | ],
1336 | "globalxp": [
1337 | "globalxp"
1338 | ],
1339 | "golems": [
1340 | "golems"
1341 | ],
1342 | "googlyeyes": [
1343 | "googlyeyes"
1344 | ],
1345 | "grapplemod": [
1346 | "grapplemod"
1347 | ],
1348 | "gravestone": [
1349 | "gravestone",
1350 | "gravestone-gravestone-mod-graves"
1351 | ],
1352 | "gravisuite": [
1353 | "gravisuite"
1354 | ],
1355 | "gravitygun": [
1356 | "gravitygun"
1357 | ],
1358 | "gregblockutils": [
1359 | "gregtech-gregblock-utilities"
1360 | ],
1361 | "gregtech": [
1362 | "gregtech"
1363 | ],
1364 | "grimoireofgaia": [
1365 | "grimoireofgaia"
1366 | ],
1367 | "grue": [
1368 | "grue"
1369 | ],
1370 | "gtadditions": [
1371 | "gregtech-gregic-additions",
1372 | "gtadditions"
1373 | ],
1374 | "gtcebees": [
1375 | "gtcebees"
1376 | ],
1377 | "gtclassic": [
1378 | "ic2"
1379 | ],
1380 | "gtconstruct": [
1381 | "gregtech-gregs-construct"
1382 | ],
1383 | "guideapi": [
1384 | "guideapi"
1385 | ],
1386 | "guielevator": [
1387 | "guielevator"
1388 | ],
1389 | "gvc": [
1390 | "gvc"
1391 | ],
1392 | "gyth": [
1393 | "gyth"
1394 | ],
1395 | "hammercore": [
1396 | "hammercore"
1397 | ],
1398 | "hammertime": [
1399 | "hammertime"
1400 | ],
1401 | "hardcoremapreset": [
1402 | "hardcoremapreset"
1403 | ],
1404 | "hardcorequesting": [
1405 | "hardcorequesting"
1406 | ],
1407 | "harderfarming": [
1408 | "harderfarming"
1409 | ],
1410 | "harderores": [
1411 | "harderores"
1412 | ],
1413 | "harvestcraft": [
1414 | "harvestcraft"
1415 | ],
1416 | "hatchery": [
1417 | "hatchery"
1418 | ],
1419 | "hatedmobs": [
1420 | "hatedmobs"
1421 | ],
1422 | "hats": [
1423 | "hats"
1424 | ],
1425 | "headcrumbs": [
1426 | "headcrumbs"
1427 | ],
1428 | "heartdrops": [
1429 | "heartdrops"
1430 | ],
1431 | "hitwithaxe": [
1432 | "hitwithaxe"
1433 | ],
1434 | "hooked": [
1435 | "hooked"
1436 | ],
1437 | "hopperducts": [
1438 | "hopperducts"
1439 | ],
1440 | "horsepower": [
1441 | "horsepower"
1442 | ],
1443 | "horsetweaks": [
1444 | "horsetweaks"
1445 | ],
1446 | "hotornot": [
1447 | "hotornot"
1448 | ],
1449 | "hungeroverhaul": [
1450 | "hungeroverhaul"
1451 | ],
1452 | "hungryanimals": [
1453 | "hungryanimals"
1454 | ],
1455 | "hungrymechanics": [
1456 | "hungrymechanics"
1457 | ],
1458 | "huntingdim": [
1459 | "huntingdim"
1460 | ],
1461 | "hydrogel": [
1462 | "hydrogel"
1463 | ],
1464 | "ic2": [
1465 | "ic2"
1466 | ],
1467 | "ic2-classic-spmod": [
1468 | "ic2"
1469 | ],
1470 | "ichunutil": [
1471 | "ichunutil",
1472 | "ichunutil-pigutils"
1473 | ],
1474 | "igwmod": [
1475 | "igwmod"
1476 | ],
1477 | "iidy": [
1478 | "iidy"
1479 | ],
1480 | "immcraft": [
1481 | "immcraft"
1482 | ],
1483 | "immersivecables": [
1484 | "immersivecables"
1485 | ],
1486 | "immersiveengineering": [
1487 | "immersiveengineering"
1488 | ],
1489 | "immersivehempcraft": [
1490 | "immersivehempcraft"
1491 | ],
1492 | "immersivepetroleum": [
1493 | "immersivepetroleum"
1494 | ],
1495 | "immersiverailroading": [
1496 | "immersiverailroading"
1497 | ],
1498 | "immersivetech": [
1499 | "immersivetech"
1500 | ],
1501 | "improvableskills": [
1502 | "improvableskills"
1503 | ],
1504 | "improvedbackpacks": [
1505 | "improvedbackpacks"
1506 | ],
1507 | "improvedextraction": [
1508 | "improvedextraction"
1509 | ],
1510 | "indlog": [
1511 | "indlog"
1512 | ],
1513 | "industrialforegoing": [
1514 | "industrialforegoing"
1515 | ],
1516 | "industrialmeat": [
1517 | "industrialmeat"
1518 | ],
1519 | "industrialrenewal": [
1520 | "industrialrenewal"
1521 | ],
1522 | "industrialwires": [
1523 | "industrialwires"
1524 | ],
1525 | "infoaccessories": [
1526 | "infoaccessories"
1527 | ],
1528 | "ingameinfoxml": [
1529 | "ingameinfo"
1530 | ],
1531 | "inspirations": [
1532 | "inspirations"
1533 | ],
1534 | "integratedcrafting": [
1535 | "integratedcrafting"
1536 | ],
1537 | "integrateddynamicscompat": [
1538 | "integrateddynamics",
1539 | "integrateddynamicscompat"
1540 | ],
1541 | "integratedrest": [
1542 | "integratedrest"
1543 | ],
1544 | "integratedterminalscompat": [
1545 | "integratedterminals",
1546 | "integratedterminalscompat"
1547 | ],
1548 | "integratedtunnelscompat": [
1549 | "integratedtunnels"
1550 | ],
1551 | "integrationforegoing": [
1552 | "integrationforegoing"
1553 | ],
1554 | "intwheel": [
1555 | "intwheel"
1556 | ],
1557 | "inventorygenerators": [
1558 | "inventorygenerators"
1559 | ],
1560 | "inventorypets": [
1561 | "inventorypets"
1562 | ],
1563 | "inventorysorter": [
1564 | "inventorysorter"
1565 | ],
1566 | "inventorytweaks": [
1567 | "inventorytweaks"
1568 | ],
1569 | "ironbackpacks": [
1570 | "ironbackpacks"
1571 | ],
1572 | "ironchest": [
1573 | "ironchest"
1574 | ],
1575 | "ironjetpacks": [
1576 | "ironjetpacks"
1577 | ],
1578 | "irontanks": [
1579 | "irontanks"
1580 | ],
1581 | "itemblacklist": [
1582 | "itemblacklist"
1583 | ],
1584 | "itemfilters": [
1585 | "itemfilters"
1586 | ],
1587 | "itemscroller": [
1588 | "itemscroller"
1589 | ],
1590 | "itemstages": [
1591 | "itemstages"
1592 | ],
1593 | "itemzoom": [
1594 | "itemzoom"
1595 | ],
1596 | "jaff": [
1597 | "jaff"
1598 | ],
1599 | "jaopca": [
1600 | "jaopca"
1601 | ],
1602 | "jaopcaadditions": [
1603 | "jaopca-jaopcaadditions"
1604 | ],
1605 | "jaopcasingularities": [
1606 | "jaopca-jaopcasingularities"
1607 | ],
1608 | "jarm": [
1609 | "jarm"
1610 | ],
1611 | "jee": [
1612 | "jee"
1613 | ],
1614 | "jehc": [
1615 | "jehc"
1616 | ],
1617 | "jei": [
1618 | "jei"
1619 | ],
1620 | "jeibees": [
1621 | "jeibees"
1622 | ],
1623 | "jeiintegration": [
1624 | "jeiintegration"
1625 | ],
1626 | "jepb": [
1627 | "jepb"
1628 | ],
1629 | "jeresources": [
1630 | "jeresources"
1631 | ],
1632 | "jetif": [
1633 | "jetif"
1634 | ],
1635 | "jmapstages": [
1636 | "jmapstages"
1637 | ],
1638 | "journeymap": [
1639 | "journeymap"
1640 | ],
1641 | "jurassicraft": [
1642 | "jurassicraft"
1643 | ],
1644 | "justanothersnad": [
1645 | "justanothersnad"
1646 | ],
1647 | "justenoughdimensions": [
1648 | "justenoughdimensions"
1649 | ],
1650 | "justenoughpetroleum": [
1651 | "justenoughpetroleum"
1652 | ],
1653 | "justenoughreactors": [
1654 | "justenoughreactors"
1655 | ],
1656 | "justthetips": [
1657 | "justthetips"
1658 | ],
1659 | "karatgarden": [
1660 | "karatgarden"
1661 | ],
1662 | "keywizard": [
1663 | "keywizard"
1664 | ],
1665 | "kjlib": [
1666 | "kjlib"
1667 | ],
1668 | "kk": [
1669 | "kk"
1670 | ],
1671 | "knowledgeshare": [
1672 | "knowledgeshare"
1673 | ],
1674 | "kwimm": [
1675 | "kwimm"
1676 | ],
1677 | "landcraft": [
1678 | "landcraft"
1679 | ],
1680 | "launchgui": [
1681 | "launchgui"
1682 | ],
1683 | "lcrdrfs": [
1684 | "lcrdrfs"
1685 | ],
1686 | "leopardengruen37": [
1687 | "leopardengruen37"
1688 | ],
1689 | "levels": [
1690 | "levels"
1691 | ],
1692 | "levelup2": [
1693 | "levelup2"
1694 | ],
1695 | "librarianlib": [
1696 | "librarianlib"
1697 | ],
1698 | "librarianliblate": [
1699 | "librarianlib"
1700 | ],
1701 | "libvulpes": [
1702 | "libvulpes"
1703 | ],
1704 | "littletiles": [
1705 | "littletiles"
1706 | ],
1707 | "llibrary": [
1708 | "llibrary"
1709 | ],
1710 | "llor": [
1711 | "llor"
1712 | ],
1713 | "lmreengaged": [
1714 | "lmreengaged"
1715 | ],
1716 | "login_shield": [
1717 | "loginshield"
1718 | ],
1719 | "logisticspipes": [
1720 | "logisticspipes"
1721 | ],
1722 | "longfallboots": [
1723 | "longfallboots"
1724 | ],
1725 | "lootbags": [
1726 | "lootbags"
1727 | ],
1728 | "lootcapacitortooltips": [
1729 | "lootcapacitortooltips"
1730 | ],
1731 | "loottweaker": [
1732 | "loottweaker"
1733 | ],
1734 | "lordcraft": [
1735 | "lordcraft"
1736 | ],
1737 | "lostcities": [
1738 | "lostcities"
1739 | ],
1740 | "lteleporters": [
1741 | "lteleporters"
1742 | ],
1743 | "lttweaker": [
1744 | "lttweaks"
1745 | ],
1746 | "lucky": [
1747 | "lucky"
1748 | ],
1749 | "lucraftcore": [
1750 | "lucraftcore"
1751 | ],
1752 | "lunatriuscore": [
1753 | "core"
1754 | ],
1755 | "magicalcrops": [
1756 | "magicalcrops"
1757 | ],
1758 | "magicalwings": [
1759 | "magicalwings"
1760 | ],
1761 | "magicbees": [
1762 | "magicbees"
1763 | ],
1764 | "magicfeather": [
1765 | "magicfeather"
1766 | ],
1767 | "magma_monsters": [
1768 | "magma_monsters"
1769 | ],
1770 | "magneticraft": [
1771 | "magneticraft"
1772 | ],
1773 | "malisisblocks": [
1774 | "malisisblocks"
1775 | ],
1776 | "malisiscore": [
1777 | "malisiscore"
1778 | ],
1779 | "malisisdoors": [
1780 | "malisisdoors"
1781 | ],
1782 | "manavisualizer": [
1783 | "manavisualizer"
1784 | ],
1785 | "mapgadgets": [
1786 | "mapgadgets"
1787 | ],
1788 | "mapwriter": [
1789 | "mapwriter"
1790 | ],
1791 | "marblecraftingtable": [
1792 | "marblecraftingtable"
1793 | ],
1794 | "matc": [
1795 | "matc"
1796 | ],
1797 | "matteroverdrive": [
1798 | "matteroverdrive"
1799 | ],
1800 | "mca": [
1801 | "mca"
1802 | ],
1803 | "meecreeps": [
1804 | "meecreeps"
1805 | ],
1806 | "megaloot": [
1807 | "megaloot"
1808 | ],
1809 | "mekanism": [
1810 | "mekanism"
1811 | ],
1812 | "metalchests": [
1813 | "metalchests"
1814 | ],
1815 | "microblockcbe": [
1816 | "multipart"
1817 | ],
1818 | "mightyenderchicken": [
1819 | "mightyenderchicken"
1820 | ],
1821 | "mikesmodslib": [
1822 | "mikesmodslib"
1823 | ],
1824 | "millenaire": [
1825 | "millenaire"
1826 | ],
1827 | "minecolonies": [
1828 | "minecolonies",
1829 | "placementpreview"
1830 | ],
1831 | "minecraftmultipartcbe": [
1832 | "multipart"
1833 | ],
1834 | "minemenu": [
1835 | "menu"
1836 | ],
1837 | "minetogether": [
1838 | "creeperhost",
1839 | "minetogetherserver"
1840 | ],
1841 | "minetogetherserver": [
1842 | "creeperhost",
1843 | "minetogetherserver"
1844 | ],
1845 | "minicoal": [
1846 | "minicoal"
1847 | ],
1848 | "miniheads": [
1849 | "miniheads"
1850 | ],
1851 | "missing_pieces": [
1852 | "missing_pieces"
1853 | ],
1854 | "mist": [
1855 | "mist"
1856 | ],
1857 | "moarboats": [
1858 | "moarboats"
1859 | ],
1860 | "moarsigns": [
1861 | "moarsigns"
1862 | ],
1863 | "moartinkers": [
1864 | "moartinkers"
1865 | ],
1866 | "mob_grinding_utils": [
1867 | "mob_grinding_utils"
1868 | ],
1869 | "mobdismemberment": [
1870 | "mobdismemberment"
1871 | ],
1872 | "mobends": [
1873 | "mobends"
1874 | ],
1875 | "mobtotems": [
1876 | "mobtotems",
1877 | "mobtotemsguide"
1878 | ],
1879 | "mocreatures": [
1880 | "mocreatures"
1881 | ],
1882 | "modcurrency": [
1883 | "modcurrency"
1884 | ],
1885 | "modnametooltip": [
1886 | "modnametooltip"
1887 | ],
1888 | "modulardiversity": [
1889 | "modulardiversity"
1890 | ],
1891 | "modularforcefieldsystem": [
1892 | "modularforcefieldsystem"
1893 | ],
1894 | "modularmachinery": [
1895 | "modularmachinery"
1896 | ],
1897 | "modularrouters": [
1898 | "modularrouters"
1899 | ],
1900 | "monk": [
1901 | "monk"
1902 | ],
1903 | "moofluids": [
1904 | "moofluids"
1905 | ],
1906 | "morebeautifulbuttons": [
1907 | "morebeautifulbuttons"
1908 | ],
1909 | "morebees": [
1910 | "morebees"
1911 | ],
1912 | "morebuckets": [
1913 | "morebuckets"
1914 | ],
1915 | "morecauldrons": [
1916 | "morecauldrons"
1917 | ],
1918 | "morechickens": [
1919 | "morechickens"
1920 | ],
1921 | "morefurnaces": [
1922 | "morefurnaces"
1923 | ],
1924 | "moreoverlays": [
1925 | "moreoverlays"
1926 | ],
1927 | "moreplates": [
1928 | "moreplates"
1929 | ],
1930 | "moreplayermodels": [
1931 | "moreplayermodels-more-player-models"
1932 | ],
1933 | "morph": [
1934 | "morph"
1935 | ],
1936 | "morphtool": [
1937 | "morphtool"
1938 | ],
1939 | "mowziesmobs": [
1940 | "mowziesmobs"
1941 | ],
1942 | "mputils": [
1943 | "mputils"
1944 | ],
1945 | "mts": [
1946 | "mts"
1947 | ],
1948 | "mtsofficialpack": [
1949 | "mtsofficialpack"
1950 | ],
1951 | "multibags": [
1952 | "multibags"
1953 | ],
1954 | "multimob": [
1955 | "primitivemobs"
1956 | ],
1957 | "multiplelights": [
1958 | "multiplelights"
1959 | ],
1960 | "multistorage": [
1961 | "multistorage"
1962 | ],
1963 | "mundaneredstone": [
1964 | "mundaneredstone"
1965 | ],
1966 | "mw": [
1967 | "mw"
1968 | ],
1969 | "mylittlemobgrinder": [
1970 | "mylittlemobgrinder"
1971 | ],
1972 | "mystcraft": [
1973 | "mystcraft"
1974 | ],
1975 | "mysticalagradditions": [
1976 | "mysticalagradditions"
1977 | ],
1978 | "mysticalagriculture": [
1979 | "mysticalagriculture"
1980 | ],
1981 | "mysticallib": [
1982 | "mysticallib"
1983 | ],
1984 | "mysticalmechanics": [
1985 | "mysticalmechanics"
1986 | ],
1987 | "natura": [
1988 | "natura"
1989 | ],
1990 | "naturesaura": [
1991 | "naturesaura"
1992 | ],
1993 | "naturescompass": [
1994 | "naturescompass"
1995 | ],
1996 | "neat": [
1997 | "neat"
1998 | ],
1999 | "nei": [
2000 | "nei"
2001 | ],
2002 | "netherendingores": [
2003 | "netherendingores"
2004 | ],
2005 | "nethermetals": [
2006 | "nethermetals"
2007 | ],
2008 | "netheroverload": [
2009 | "netheroverload"
2010 | ],
2011 | "netherportalfix": [
2012 | "netherportalfix"
2013 | ],
2014 | "netherutils": [
2015 | "netherutils"
2016 | ],
2017 | "neverenoughcandy": [
2018 | "neverenoughcandy"
2019 | ],
2020 | "nex": [
2021 | "nex"
2022 | ],
2023 | "nhc": [
2024 | "nhc"
2025 | ],
2026 | "nibbler": [
2027 | "nibbler"
2028 | ],
2029 | "nice": [
2030 | "nice"
2031 | ],
2032 | "no_matter": [
2033 | "no_matter"
2034 | ],
2035 | "noodle": [
2036 | "noodle"
2037 | ],
2038 | "notenoughroofs": [
2039 | "notenoughroofs"
2040 | ],
2041 | "notenoughwands": [
2042 | "notenoughwands"
2043 | ],
2044 | "nuclearcraft": [
2045 | "nuclearcraft"
2046 | ],
2047 | "numina": [
2048 | "numina"
2049 | ],
2050 | "ocsensors": [
2051 | "ocsensors"
2052 | ],
2053 | "oeintegration": [
2054 | "oeintegration"
2055 | ],
2056 | "of": [
2057 | "of"
2058 | ],
2059 | "ogdragon": [
2060 | "gdaddons",
2061 | "ogdragon"
2062 | ],
2063 | "oldjava": [
2064 | "oldjava"
2065 | ],
2066 | "omlib": [
2067 | "omlib"
2068 | ],
2069 | "ompd": [
2070 | "ompd"
2071 | ],
2072 | "openblocks": [
2073 | "openblocks"
2074 | ],
2075 | "opencomputers": [
2076 | "opencomputers"
2077 | ],
2078 | "openglider": [
2079 | "openglider"
2080 | ],
2081 | "openmods": [
2082 | "openmods"
2083 | ],
2084 | "openmodularturrets": [
2085 | "openmodularturrets"
2086 | ],
2087 | "openprinter": [
2088 | "openprinter"
2089 | ],
2090 | "opensecurity": [
2091 | "opensecurity"
2092 | ],
2093 | "openterraingenerator": [
2094 | "openterraingenerator"
2095 | ],
2096 | "opframe": [
2097 | "opframe"
2098 | ],
2099 | "oreberries": [
2100 | "oreberries"
2101 | ],
2102 | "oredictinit": [
2103 | "jaopca"
2104 | ],
2105 | "oredictreorder": [
2106 | "oredictreorder"
2107 | ],
2108 | "oreexcavation": [
2109 | "oreexcavation"
2110 | ],
2111 | "oreflowers": [
2112 | "oreflowers"
2113 | ],
2114 | "orelib": [
2115 | "orelib"
2116 | ],
2117 | "overloaded": [
2118 | "overloaded"
2119 | ],
2120 | "p455w0rdslib": [
2121 | "p455w0rdslib"
2122 | ],
2123 | "packagedauto": [
2124 | "packagedauto"
2125 | ],
2126 | "packingtape": [
2127 | "packingtape"
2128 | ],
2129 | "packlack": [
2130 | "primitivemobs"
2131 | ],
2132 | "packmode": [
2133 | "packmode"
2134 | ],
2135 | "parabox": [
2136 | "parabox"
2137 | ],
2138 | "parry": [
2139 | "parry"
2140 | ],
2141 | "patchouli": [
2142 | "patchouli"
2143 | ],
2144 | "pdp": [
2145 | "proportionaldestructionparticles"
2146 | ],
2147 | "peripheralsplusone": [
2148 | "peripheralsplusone"
2149 | ],
2150 | "persistentbits": [
2151 | "persistentbits"
2152 | ],
2153 | "personalcars": [
2154 | "personalcars"
2155 | ],
2156 | "personaleffects": [
2157 | "vanillafoodpantry"
2158 | ],
2159 | "petrock": [
2160 | "petrock"
2161 | ],
2162 | "petslow": [
2163 | "petslow"
2164 | ],
2165 | "pewter": [
2166 | "pewter"
2167 | ],
2168 | "pickletweaks": [
2169 | "pickletweaks"
2170 | ],
2171 | "ping": [
2172 | "ping"
2173 | ],
2174 | "pinklysheep": [
2175 | "vanillafoodpantry"
2176 | ],
2177 | "pipemaster": [
2178 | "pipemaster"
2179 | ],
2180 | "pizzacraft": [
2181 | "pizzacraft"
2182 | ],
2183 | "placeableitems": [
2184 | "placeableitems"
2185 | ],
2186 | "planarartifice": [
2187 | "planarartifice"
2188 | ],
2189 | "planetprogression": [
2190 | "planetprogression"
2191 | ],
2192 | "plants2": [
2193 | "plants2"
2194 | ],
2195 | "platforms": [
2196 | "platforms"
2197 | ],
2198 | "playerplates": [
2199 | "playerplates"
2200 | ],
2201 | "playersdropheads": [
2202 | "playersdropheads"
2203 | ],
2204 | "playerskins": [
2205 | "playerskins"
2206 | ],
2207 | "plethora": [
2208 | "plethora"
2209 | ],
2210 | "plethora-core": [
2211 | "plethora"
2212 | ],
2213 | "plustic": [
2214 | "plustic"
2215 | ],
2216 | "pmp": [
2217 | "pmp"
2218 | ],
2219 | "pneumaticcraft": [
2220 | "pneumaticcraft"
2221 | ],
2222 | "pokecube_adventures": [
2223 | "pokecube_adventures",
2224 | "pokecube_compat"
2225 | ],
2226 | "pokecube_compat": [
2227 | "pokecube_adventures",
2228 | "pokecube_compat"
2229 | ],
2230 | "portalgun": [
2231 | "portalgun"
2232 | ],
2233 | "portality": [
2234 | "portality"
2235 | ],
2236 | "potioncore": [
2237 | "potioncore"
2238 | ],
2239 | "potioncraft": [
2240 | "potioncraft"
2241 | ],
2242 | "poweradapters": [
2243 | "poweradapters"
2244 | ],
2245 | "powerchisels": [
2246 | "powerchisels"
2247 | ],
2248 | "powersuits": [
2249 | "powersuits"
2250 | ],
2251 | "practicallogistics2": [
2252 | "practicallogistics2"
2253 | ],
2254 | "prefab": [
2255 | "prefab"
2256 | ],
2257 | "pressure": [
2258 | "pressure"
2259 | ],
2260 | "prestige": [
2261 | "prestige"
2262 | ],
2263 | "primal": [
2264 | "primal"
2265 | ],
2266 | "primal_tech": [
2267 | "primal_tech"
2268 | ],
2269 | "primalchests": [
2270 | "primalchests"
2271 | ],
2272 | "primitivecrafting": [
2273 | "primitivecrafting"
2274 | ],
2275 | "primitivemobs": [
2276 | "primitivemobs"
2277 | ],
2278 | "progressiontweaks": [
2279 | "progressiontweaks"
2280 | ],
2281 | "progressiveautomation": [
2282 | "progressiveautomation"
2283 | ],
2284 | "progressivebosses": [
2285 | "progressivebosses"
2286 | ],
2287 | "progressivecore": [
2288 | "progressivecore"
2289 | ],
2290 | "projecte": [
2291 | "projecte"
2292 | ],
2293 | "projectex": [
2294 | "projectex"
2295 | ],
2296 | "projectintelligence": [
2297 | "projectintelligence"
2298 | ],
2299 | "projectred-compat": [
2300 | "projectred"
2301 | ],
2302 | "projectred-core": [
2303 | "projectred"
2304 | ],
2305 | "projectred-expansion": [
2306 | "projectred"
2307 | ],
2308 | "projectred-exploration": [
2309 | "projectred"
2310 | ],
2311 | "projectred-fabrication": [
2312 | "projectred"
2313 | ],
2314 | "projectred-illumination": [
2315 | "projectred"
2316 | ],
2317 | "projectred-integration": [
2318 | "projectred"
2319 | ],
2320 | "projectred-relocation": [
2321 | "projectred"
2322 | ],
2323 | "projectred-transmission": [
2324 | "projectred"
2325 | ],
2326 | "projectred-transportation": [
2327 | "projectred"
2328 | ],
2329 | "projectx": [
2330 | "projectx"
2331 | ],
2332 | "props": [
2333 | "props"
2334 | ],
2335 | "prospectors": [
2336 | "prospectors"
2337 | ],
2338 | "psi": [
2339 | "psi"
2340 | ],
2341 | "pvj": [
2342 | "pvj"
2343 | ],
2344 | "quacklib": [
2345 | "quacklib"
2346 | ],
2347 | "qualitytools": [
2348 | "qualitytools"
2349 | ],
2350 | "quantumflux": [
2351 | "quantumflux"
2352 | ],
2353 | "quantumstorage": [
2354 | "quantumstorage"
2355 | ],
2356 | "quark": [
2357 | "quark"
2358 | ],
2359 | "quarryplus": [
2360 | "quarryplus"
2361 | ],
2362 | "quarryplus_ct": [
2363 | "quarryplus"
2364 | ],
2365 | "questbook": [
2366 | "questbook"
2367 | ],
2368 | "questionablyimmersive": [
2369 | "questionablyimmersive"
2370 | ],
2371 | "questutils": [
2372 | "questutils"
2373 | ],
2374 | "r2s_c": [
2375 | "r2s_c"
2376 | ],
2377 | "r2s_r": [
2378 | "r2s_r"
2379 | ],
2380 | "railcraft": [
2381 | "railcraft"
2382 | ],
2383 | "rainboaks": [
2384 | "rainboaks"
2385 | ],
2386 | "randomloot": [
2387 | "randomloot"
2388 | ],
2389 | "randompatches": [
2390 | "randompatches"
2391 | ],
2392 | "randomthings": [
2393 | "randomthings"
2394 | ],
2395 | "randomtweaks": [
2396 | "randomtweaks"
2397 | ],
2398 | "randomutilities": [
2399 | "randomutilities"
2400 | ],
2401 | "rangedpumps": [
2402 | "rangedpumps"
2403 | ],
2404 | "rarmor": [
2405 | "rarmor"
2406 | ],
2407 | "rats": [
2408 | "rats"
2409 | ],
2410 | "realfilingcabinet": [
2411 | "realfilingcabinet"
2412 | ],
2413 | "realworld": [
2414 | "realworld"
2415 | ],
2416 | "reborncore": [
2417 | "reborncore"
2418 | ],
2419 | "rebornstorage": [
2420 | "rebornstorage"
2421 | ],
2422 | "reccomplex": [
2423 | "mcopts",
2424 | "reccomplex"
2425 | ],
2426 | "recipehandler": [
2427 | "recipehandler"
2428 | ],
2429 | "recipestages": [
2430 | "recipestages"
2431 | ],
2432 | "redstonearsenal": [
2433 | "redstonearsenal"
2434 | ],
2435 | "redstonepaste": [
2436 | "redstonepaste"
2437 | ],
2438 | "redstonic": [
2439 | "redstonic"
2440 | ],
2441 | "refinedrelocation": [
2442 | "refinedrelocation"
2443 | ],
2444 | "refinedstorage": [
2445 | "refinedstorage"
2446 | ],
2447 | "refinedstorageaddons": [
2448 | "refinedstorageaddons"
2449 | ],
2450 | "reforged": [
2451 | "reforged"
2452 | ],
2453 | "refraction": [
2454 | "refraction"
2455 | ],
2456 | "repairgem": [
2457 | "repairgem"
2458 | ],
2459 | "resize": [
2460 | "resize"
2461 | ],
2462 | "reskillable": [
2463 | "reskillable"
2464 | ],
2465 | "resourcehogs": [
2466 | "resourcehogs"
2467 | ],
2468 | "resourcereloader": [
2469 | "resourcereloader"
2470 | ],
2471 | "restrictedportals": [
2472 | "restrictedportals"
2473 | ],
2474 | "retroexchange": [
2475 | "retroexchange"
2476 | ],
2477 | "rewired": [
2478 | "rewired"
2479 | ],
2480 | "rf-capability-adapter": [
2481 | "rf-capability-adapter"
2482 | ],
2483 | "rflux": [
2484 | "rflux"
2485 | ],
2486 | "rftools": [
2487 | "rftools"
2488 | ],
2489 | "rftoolscontrol": [
2490 | "rftoolscontrol"
2491 | ],
2492 | "rftoolsdim": [
2493 | "rftoolsdim"
2494 | ],
2495 | "rftoolspower": [
2496 | "rftoolspower"
2497 | ],
2498 | "riteclicker": [
2499 | "riteclicker"
2500 | ],
2501 | "rockcandy": [
2502 | "rockcandy"
2503 | ],
2504 | "rockhounding_chemistry": [
2505 | "rockhounding_chemistry"
2506 | ],
2507 | "rockhounding_core": [
2508 | "rockhounding_core"
2509 | ],
2510 | "rockhounding_oretiers": [
2511 | "rockhounding_oretiers"
2512 | ],
2513 | "rockhounding_rocks": [
2514 | "rockhounding_rocks"
2515 | ],
2516 | "rockhounding_surface": [
2517 | "rockhounding_surface"
2518 | ],
2519 | "roost": [
2520 | "roost"
2521 | ],
2522 | "roots": [
2523 | "roots"
2524 | ],
2525 | "rootsclassic": [
2526 | "rootsclassic"
2527 | ],
2528 | "ropebridge": [
2529 | "ropebridge"
2530 | ],
2531 | "roughtweaks": [
2532 | "roughtweaks"
2533 | ],
2534 | "row": [
2535 | "row"
2536 | ],
2537 | "rpsideas": [
2538 | "rpsideas"
2539 | ],
2540 | "rswires": [
2541 | "rswires"
2542 | ],
2543 | "rustic": [
2544 | "rustic"
2545 | ],
2546 | "rusticthaumaturgy": [
2547 | "rusticthaumaturgy"
2548 | ],
2549 | "sc": [
2550 | "sc"
2551 | ],
2552 | "sc2": [
2553 | "sc2"
2554 | ],
2555 | "scalinghealth": [
2556 | "scalinghealth"
2557 | ],
2558 | "scannable": [
2559 | "scannable"
2560 | ],
2561 | "scanner": [
2562 | "scanner"
2563 | ],
2564 | "schematica": [
2565 | "schematica"
2566 | ],
2567 | "secretroomsmod": [
2568 | "secretroomsmod"
2569 | ],
2570 | "securitycraft": [
2571 | "securitycraft"
2572 | ],
2573 | "sevtweaks": [
2574 | "sevtweaks"
2575 | ],
2576 | "sgextraparts": [
2577 | "sgextraparts"
2578 | ],
2579 | "shadowmc": [
2580 | "shadowmc"
2581 | ],
2582 | "shear": [
2583 | "shear"
2584 | ],
2585 | "shearmadness": [
2586 | "shearmadness"
2587 | ],
2588 | "shetiphiancore": [
2589 | "shetiphiancore"
2590 | ],
2591 | "shootit": [
2592 | "shootit"
2593 | ],
2594 | "signals": [
2595 | "signals"
2596 | ],
2597 | "signpic": [
2598 | "signpic"
2599 | ],
2600 | "signpost": [
2601 | "signpost"
2602 | ],
2603 | "silentgems": [
2604 | "silentgems"
2605 | ],
2606 | "silentlib": [
2607 | "silentlib"
2608 | ],
2609 | "silverfish": [
2610 | "silverfish"
2611 | ],
2612 | "similsaxtranstructors": [
2613 | "similsaxtranstructors"
2614 | ],
2615 | "simple_trophies": [
2616 | "simple_trophies"
2617 | ],
2618 | "simpleautorun": [
2619 | "simpleautorun"
2620 | ],
2621 | "simplecobblegen": [
2622 | "simplecobblegen"
2623 | ],
2624 | "simplecorn": [
2625 | "simplecorn"
2626 | ],
2627 | "simplegenerators": [
2628 | "simplegenerators"
2629 | ],
2630 | "simplegrinder": [
2631 | "simplegrinder"
2632 | ],
2633 | "simpleoregen": [
2634 | "simpleoregen"
2635 | ],
2636 | "simplesponge": [
2637 | "simplesponge"
2638 | ],
2639 | "simpletcon": [
2640 | "simpletcon",
2641 | "tconstruct-simple-tcon"
2642 | ],
2643 | "simpleundergroundbiomes": [
2644 | "simpleundergroundbiomes"
2645 | ],
2646 | "simplevoidworld": [
2647 | "simplevoidworld"
2648 | ],
2649 | "simplyarrows": [
2650 | "simplyarrows"
2651 | ],
2652 | "simplybackpacks": [
2653 | "simplybackpacks"
2654 | ],
2655 | "simplyconveyors": [
2656 | "simplyconveyors"
2657 | ],
2658 | "simplyjetpacks": [
2659 | "simplyjetpacks"
2660 | ],
2661 | "simplylight": [
2662 | "performantlighting",
2663 | "simplylight"
2664 | ],
2665 | "simulatednights": [
2666 | "simulatednights"
2667 | ],
2668 | "sipl": [
2669 | "sipl"
2670 | ],
2671 | "sky_orchards": [
2672 | "sky_orchards"
2673 | ],
2674 | "skybonsais": [
2675 | "skybonsais"
2676 | ],
2677 | "skygrid": [
2678 | "skygrid"
2679 | ],
2680 | "skylandsforge": [
2681 | "skylandsforge"
2682 | ],
2683 | "skyresources": [
2684 | "skyresources"
2685 | ],
2686 | "slabmachines": [
2687 | "slabmachines"
2688 | ],
2689 | "smarthoppers": [
2690 | "vanillafoodpantry"
2691 | ],
2692 | "smoothfont": [
2693 | "smoothfont"
2694 | ],
2695 | "snad": [
2696 | "snad"
2697 | ],
2698 | "snowvariants": [
2699 | "snowvariants"
2700 | ],
2701 | "solarflux": [
2702 | "solarflux"
2703 | ],
2704 | "solcarrot": [
2705 | "solcarrot"
2706 | ],
2707 | "somanyenchantments": [
2708 | "somanyenchantments"
2709 | ],
2710 | "sonarcore": [
2711 | "sonarcore"
2712 | ],
2713 | "soot": [
2714 | "soot"
2715 | ],
2716 | "sophisticatedwolves": [
2717 | "sophisticatedwolves"
2718 | ],
2719 | "soulshardsrespawn": [
2720 | "soulshardsrespawn"
2721 | ],
2722 | "soulshardstow": [
2723 | "soulshardstow"
2724 | ],
2725 | "soulus": [
2726 | "soulus"
2727 | ],
2728 | "spacecraftx": [
2729 | "spacecraftx"
2730 | ],
2731 | "sparkshammers": [
2732 | "sparkshammers"
2733 | ],
2734 | "spartanshields": [
2735 | "spartanshields"
2736 | ],
2737 | "spawnercontrol": [
2738 | "spawnercontrol"
2739 | ],
2740 | "specialai": [
2741 | "specialai"
2742 | ],
2743 | "specialmobs": [
2744 | "specialmobs"
2745 | ],
2746 | "speedsterheroes": [
2747 | "speedsterheroes"
2748 | ],
2749 | "spiceoflife": [
2750 | "spiceoflife"
2751 | ],
2752 | "spikemod": [
2753 | "spikemod"
2754 | ],
2755 | "stackie": [
2756 | "stackie"
2757 | ],
2758 | "starcraft": [
2759 | "starcraft"
2760 | ],
2761 | "stats_keeper": [
2762 | "stats_keeper"
2763 | ],
2764 | "statues": [
2765 | "statues"
2766 | ],
2767 | "stepup": [
2768 | "stepup"
2769 | ],
2770 | "stevescarts": [
2771 | "stevescarts"
2772 | ],
2773 | "stoneblockutilities": [
2774 | "stoneblockutilities"
2775 | ],
2776 | "stonechest": [
2777 | "stonechest"
2778 | ],
2779 | "storagedrawers": [
2780 | "storagedrawers"
2781 | ],
2782 | "storagedrawersextra": [
2783 | "storagedrawersextra"
2784 | ],
2785 | "storagenetwork": [
2786 | "storagenetwork"
2787 | ],
2788 | "structuredcrafting": [
2789 | "structuredcrafting"
2790 | ],
2791 | "structurize": [
2792 | "structurize"
2793 | ],
2794 | "styledblocks": [
2795 | "vanillafoodpantry"
2796 | ],
2797 | "superblocks": [
2798 | "vanillafoodpantry"
2799 | ],
2800 | "superfactorymanager": [
2801 | "superfactorymanager"
2802 | ],
2803 | "superminer_captivator": [
2804 | "superminer"
2805 | ],
2806 | "superminer_core": [
2807 | "superminer"
2808 | ],
2809 | "superminer_cropinator": [
2810 | "superminer"
2811 | ],
2812 | "superminer_excavator": [
2813 | "superminer"
2814 | ],
2815 | "superminer_illuminator": [
2816 | "superminer"
2817 | ],
2818 | "superminer_lumbinator": [
2819 | "superminer"
2820 | ],
2821 | "superminer_shaftanator": [
2822 | "superminer"
2823 | ],
2824 | "superminer_substitutor": [
2825 | "superminer"
2826 | ],
2827 | "superminer_veinator": [
2828 | "superminer"
2829 | ],
2830 | "superores": [
2831 | "superores"
2832 | ],
2833 | "supersoundmuffler": [
2834 | "supersoundmuffler"
2835 | ],
2836 | "survivalist": [
2837 | "survivalist"
2838 | ],
2839 | "sync": [
2840 | "sync"
2841 | ],
2842 | "taiga": [
2843 | "taiga"
2844 | ],
2845 | "tails": [
2846 | "tails"
2847 | ],
2848 | "tallgates": [
2849 | "tallgates"
2850 | ],
2851 | "tammodized": [
2852 | "tammodized"
2853 | ],
2854 | "tanaddons": [
2855 | "tanaddons"
2856 | ],
2857 | "tcinventoryscan": [
2858 | "tcinventoryscan"
2859 | ],
2860 | "tcomplement": [
2861 | "tcomplement"
2862 | ],
2863 | "tconstruct": [
2864 | "tconstruct"
2865 | ],
2866 | "teamislands": [
2867 | "teamislands"
2868 | ],
2869 | "teamlapen-lib": [
2870 | "vampirism",
2871 | "vampirismguide"
2872 | ],
2873 | "techguns": [
2874 | "techguns"
2875 | ],
2876 | "technicallights": [
2877 | "technicallights"
2878 | ],
2879 | "technom": [
2880 | "technom"
2881 | ],
2882 | "techreborn": [
2883 | "techreborn"
2884 | ],
2885 | "telepastries": [
2886 | "telepastries"
2887 | ],
2888 | "teletoro": [
2889 | "teletoro"
2890 | ],
2891 | "terraqueous": [
2892 | "terraqueous"
2893 | ],
2894 | "tesla": [
2895 | "tesla"
2896 | ],
2897 | "teslacorelib": [
2898 | "teslacorelib"
2899 | ],
2900 | "teslacorelib_registries": [
2901 | "teslacorelib"
2902 | ],
2903 | "teslathingies": [
2904 | "teslathingies"
2905 | ],
2906 | "tesslocator": [
2907 | "tesslocator"
2908 | ],
2909 | "testdummy": [
2910 | "testdummy"
2911 | ],
2912 | "testmod": [
2913 | "vanillafoodpantry"
2914 | ],
2915 | "tetra": [
2916 | "tetra"
2917 | ],
2918 | "thaumadditions": [
2919 | "thaumadditions"
2920 | ],
2921 | "thaumcomp": [
2922 | "thaumcomp"
2923 | ],
2924 | "thaumcraft": [
2925 | "thaumcraft"
2926 | ],
2927 | "thaumicbases": [
2928 | "thaumicbases"
2929 | ],
2930 | "thaumicenergistics": [
2931 | "thaumicenergistics"
2932 | ],
2933 | "thaumicequivalence": [
2934 | "thaumicequivalence"
2935 | ],
2936 | "thaumicgrid": [
2937 | "thaumicgrid"
2938 | ],
2939 | "thaumicjei": [
2940 | "thaumicjei"
2941 | ],
2942 | "thaumicperiphery": [
2943 | "thaumicperiphery"
2944 | ],
2945 | "thaumicrestoration": [
2946 | "thaumicrestoration"
2947 | ],
2948 | "thaumicterminal": [
2949 | "thaumicterminal"
2950 | ],
2951 | "thaumictinkerer": [
2952 | "thaumictinkerer"
2953 | ],
2954 | "thebetweenlands": [
2955 | "thebetweenlands"
2956 | ],
2957 | "thebomplugin": [
2958 | "thebomplugin"
2959 | ],
2960 | "thedragonlib": [
2961 | "thedragonlib"
2962 | ],
2963 | "theframework": [
2964 | "theframework"
2965 | ],
2966 | "theoneprobe": [
2967 | "theoneprobe"
2968 | ],
2969 | "thermalcultivation": [
2970 | "thermalcultivation"
2971 | ],
2972 | "thermaldynamics": [
2973 | "thermaldynamics"
2974 | ],
2975 | "thermalexpansion": [
2976 | "thermalexpansion"
2977 | ],
2978 | "thermalfoundation": [
2979 | "thermalfoundation"
2980 | ],
2981 | "thermalinnovation": [
2982 | "thermalinnovation"
2983 | ],
2984 | "thermallogistics": [
2985 | "thermallogistics"
2986 | ],
2987 | "thermalsolars": [
2988 | "thermalsolars"
2989 | ],
2990 | "thesummoner": [
2991 | "primitivemobs"
2992 | ],
2993 | "threng": [
2994 | "threng"
2995 | ],
2996 | "thutcore": [
2997 | "thutcore"
2998 | ],
2999 | "thutcore_compat": [
3000 | "thutcore"
3001 | ],
3002 | "thuttech": [
3003 | "thuttech"
3004 | ],
3005 | "tinker_io": [
3006 | "tinker_io"
3007 | ],
3008 | "tinkersaddons": [
3009 | "tinkersaddons"
3010 | ],
3011 | "tinkersaether": [
3012 | "tinkersaether"
3013 | ],
3014 | "tinkersarsenal": [
3015 | "tinkersarsenal"
3016 | ],
3017 | "tinkerscompendium": [
3018 | "tinkerscompendium"
3019 | ],
3020 | "tinkersforging": [
3021 | "tinkersforging"
3022 | ],
3023 | "tinkersjei": [
3024 | "tinkersjei"
3025 | ],
3026 | "tinkerskyblock": [
3027 | "tinkerskyblock"
3028 | ],
3029 | "tinkersurvival": [
3030 | "tinkersurvival"
3031 | ],
3032 | "tinkertoolleveling": [
3033 | "tinkertoolleveling"
3034 | ],
3035 | "tips": [
3036 | "tips"
3037 | ],
3038 | "toastcontrol": [
3039 | "toastcontrol"
3040 | ],
3041 | "tombmanygraves": [
3042 | "tombmanygraves"
3043 | ],
3044 | "tombstone": [
3045 | "tombstone"
3046 | ],
3047 | "toolprogression": [
3048 | "toolprogression"
3049 | ],
3050 | "topaddons": [
3051 | "topaddons"
3052 | ],
3053 | "torcherino": [
3054 | "torcherino"
3055 | ],
3056 | "torchmaster": [
3057 | "torchmaster"
3058 | ],
3059 | "torohealthmod": [
3060 | "torohealthmod"
3061 | ],
3062 | "totemessentials": [
3063 | "totemessentials"
3064 | ],
3065 | "totemic": [
3066 | "totemic"
3067 | ],
3068 | "toughasnails": [
3069 | "toughasnails"
3070 | ],
3071 | "townbuilder": [
3072 | "townbuilder"
3073 | ],
3074 | "toxicrain": [
3075 | "toxicrain"
3076 | ],
3077 | "tp": [
3078 | "tp"
3079 | ],
3080 | "tracer": [
3081 | "tracer"
3082 | ],
3083 | "translocators": [
3084 | "translocators"
3085 | ],
3086 | "transprot": [
3087 | "transprot"
3088 | ],
3089 | "trashcansreborn": [
3090 | "trashcansreborn"
3091 | ],
3092 | "trashslot": [
3093 | "trashslot"
3094 | ],
3095 | "travellersbackpack": [
3096 | "travellersbackpack"
3097 | ],
3098 | "traverse": [
3099 | "traverse"
3100 | ],
3101 | "treechopper": [
3102 | "treechopper"
3103 | ],
3104 | "treegrowingsimulator": [
3105 | "treegrowingsimulator"
3106 | ],
3107 | "tropicraft": [
3108 | "tropicraft"
3109 | ],
3110 | "trumpetskeleton": [
3111 | "trumpetskeleton"
3112 | ],
3113 | "tumbleweed": [
3114 | "tumbleweed"
3115 | ],
3116 | "twitchintegration": [
3117 | "twitchintegration"
3118 | ],
3119 | "ultimate_unicorn_mod": [
3120 | "ultimate_unicorn_mod"
3121 | ],
3122 | "umm3185118519": [
3123 | "umm3185118519"
3124 | ],
3125 | "undergroundbiomes": [
3126 | "undergroundbiomes"
3127 | ],
3128 | "universalmodifiers": [
3129 | "valkyrielib"
3130 | ],
3131 | "uppers": [
3132 | "uppers"
3133 | ],
3134 | "upsizer": [
3135 | "vanillafoodpantry"
3136 | ],
3137 | "usefulglasses": [
3138 | "usefulglasses"
3139 | ],
3140 | "usefulnullifiers": [
3141 | "usefulnullifiers"
3142 | ],
3143 | "uutils": [
3144 | "uutils"
3145 | ],
3146 | "v0idssmartbackpacks": [
3147 | "v0idssmartbackpacks"
3148 | ],
3149 | "va": [
3150 | "va"
3151 | ],
3152 | "valkyrielib": [
3153 | "valkyrielib"
3154 | ],
3155 | "vampirism": [
3156 | "vampirism",
3157 | "vampirismguide"
3158 | ],
3159 | "vanillafix": [
3160 | "vanillafix"
3161 | ],
3162 | "vanillafoodpantry": [
3163 | "vanillafoodpantry"
3164 | ],
3165 | "vanillasatchels": [
3166 | "vanillasatchels"
3167 | ],
3168 | "varietymobs": [
3169 | "primitivemobs"
3170 | ],
3171 | "vbe": [
3172 | "vbe"
3173 | ],
3174 | "vc": [
3175 | "vc"
3176 | ],
3177 | "vefluids": [
3178 | "vefluids"
3179 | ],
3180 | "vehicle": [
3181 | "vehicle"
3182 | ],
3183 | "veinminer": [
3184 | "veinminer"
3185 | ],
3186 | "veinminermodsupport": [
3187 | "veinminer"
3188 | ],
3189 | "vending": [
3190 | "vending"
3191 | ],
3192 | "vials": [
3193 | "vials"
3194 | ],
3195 | "villagermarket": [
3196 | "villagermarket"
3197 | ],
3198 | "vimmersion": [
3199 | "vimmersion"
3200 | ],
3201 | "virtualmachines": [
3202 | "virtualmachines"
3203 | ],
3204 | "voidcraft": [
3205 | "voidcraft"
3206 | ],
3207 | "voidislandcontrol": [
3208 | "voidislandcontrol"
3209 | ],
3210 | "voxelmap": [
3211 | "voxelmap"
3212 | ],
3213 | "vtt": [
3214 | "vtt"
3215 | ],
3216 | "waddles": [
3217 | "waddles"
3218 | ],
3219 | "waila": [
3220 | "waila"
3221 | ],
3222 | "wailaharvestability": [
3223 | "wailaharvestability"
3224 | ],
3225 | "waim": [
3226 | "waim"
3227 | ],
3228 | "walljump": [
3229 | "walljump"
3230 | ],
3231 | "wanionlib": [
3232 | "wanionlib"
3233 | ],
3234 | "warpbook": [
3235 | "warpbook"
3236 | ],
3237 | "waterstrainer": [
3238 | "waterstrainer"
3239 | ],
3240 | "wawla": [
3241 | "wawla"
3242 | ],
3243 | "waystones": [
3244 | "waystones"
3245 | ],
3246 | "wct": [
3247 | "wct"
3248 | ],
3249 | "wearablebackpacks": [
3250 | "wearablebackpacks"
3251 | ],
3252 | "weather2": [
3253 | "weather2"
3254 | ],
3255 | "webdisplays": [
3256 | "webdisplays"
3257 | ],
3258 | "weeeflowers": [
3259 | "weeeflowers"
3260 | ],
3261 | "weirdinggadget": [
3262 | "weirdinggadget"
3263 | ],
3264 | "wft": [
3265 | "wft"
3266 | ],
3267 | "whoosh": [
3268 | "whoosh"
3269 | ],
3270 | "wildnature": [
3271 | "wildnature"
3272 | ],
3273 | "wings": [
3274 | "wings"
3275 | ],
3276 | "wirelesscharger": [
3277 | "wirelesscharger"
3278 | ],
3279 | "wit": [
3280 | "wit"
3281 | ],
3282 | "withercrumbs": [
3283 | "withercrumbs"
3284 | ],
3285 | "witherskelefix": [
3286 | "witherskelefix"
3287 | ],
3288 | "wizardry": [
3289 | "wizardry"
3290 | ],
3291 | "wolfarmor": [
3292 | "wolfarmor"
3293 | ],
3294 | "woodenbucket": [
3295 | "woodenbucket"
3296 | ],
3297 | "woodenshears": [
3298 | "woodenshears"
3299 | ],
3300 | "woot": [
3301 | "woot",
3302 | "wootguide"
3303 | ],
3304 | "wopper": [
3305 | "wopper"
3306 | ],
3307 | "worldcontrol": [
3308 | "worldcontrol"
3309 | ],
3310 | "worldprimer": [
3311 | "worldprimer"
3312 | ],
3313 | "worldutils": [
3314 | "worldutils"
3315 | ],
3316 | "wpt": [
3317 | "wpt"
3318 | ],
3319 | "wrcbe": [
3320 | "wrcbe"
3321 | ],
3322 | "xaerobetterpvp": [
3323 | "xaerobetterpvp",
3324 | "xaeroworldmap"
3325 | ],
3326 | "xaerominimap": [
3327 | "xaerobetterpvp",
3328 | "xaeroworldmap"
3329 | ],
3330 | "xaeroworldmap": [
3331 | "xaerobetterpvp",
3332 | "xaeroworldmap"
3333 | ],
3334 | "xencraft": [
3335 | "xencraft"
3336 | ],
3337 | "xlfoodmod": [
3338 | "xlfoodmod"
3339 | ],
3340 | "xnet": [
3341 | "xnet"
3342 | ],
3343 | "xreliquary": [
3344 | "xreliquary"
3345 | ],
3346 | "xtones": [
3347 | "xtones"
3348 | ],
3349 | "yabba": [
3350 | "yabba"
3351 | ],
3352 | "yamda": [
3353 | "yamda"
3354 | ],
3355 | "yoyos": [
3356 | "yoyos"
3357 | ],
3358 | "yudodis": [
3359 | "yudodis"
3360 | ],
3361 | "zenstages": [
3362 | "zenstages"
3363 | ],
3364 | "zensummoning": [
3365 | "zensummoning"
3366 | ],
3367 | "zerocore": [
3368 | "zerocore"
3369 | ],
3370 | "zettaindustries": [
3371 | "zettaindustries"
3372 | ],
3373 | "zombieawareness": [
3374 | "zombieawareness"
3375 | ]
3376 | }
3377 |
--------------------------------------------------------------------------------
/src/main/resources/assets/i18nmod/icon/pack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CFPAOrg/I18nUpdateMod/47bfc71b816e2b370bb9ac338a06b863b91c75da/src/main/resources/assets/i18nmod/icon/pack.png
--------------------------------------------------------------------------------
/src/main/resources/assets/i18nmod/lang/en_us.lang:
--------------------------------------------------------------------------------
1 | # 英文?英文是不存在的
2 |
3 | # 键位部分
4 | key.category.i18nmod=汉化更新模组键位设定
5 | key.i18nmod.main_key.desc=组合键主键位
6 | key.i18nmod.report_key.desc=问题反馈键
7 | key.i18nmod.weblate_key.desc=打开 Weblate 对应词条
8 | key.i18nmod.mcmod_key.desc=打开 Mcmod 百科对应词条
9 | key.i18nmod.reload_key.desc=快速重载汉化
10 |
11 | # 消息提醒(不硬编码,因为这一处原版强制语言文件)
12 | message.i18nmod.cmd_reload.success=§3[§6调试功能§3] §e重载成功
13 |
14 | message.i18nmod.cmd_report.empty=§e请将要反馈的物品拿在手上
15 |
16 | message.i18nmod.cmd_get_langpack.empty=§e缺失参数,正确输入应当为 §6/lang_get <模组资源id>
17 | message.i18nmod.cmd_get_langpack.not_found=§3[§6调试功能§3] §e未找到资源 id 为 §6%s §e的模组,请确认是否输入错误,或者目前没有加入翻译项目
18 | message.i18nmod.cmd_get_langpack.right_start=§3[§6调试功能§3] §e正在下载 §6%s §e模组的资源包,请稍等……
19 | message.i18nmod.cmd_get_langpack.right_downloading=§3[§6调试功能§3] §e下载中,已耗时 %d §e秒……
20 | message.i18nmod.cmd_get_langpack.right_stop=§3[§6调试功能§3] §e下载完毕,请手动加载资源包
21 | message.i18nmod.cmd_get_langpack.error_stop=§3[§6调试功能§3] §e下载失败,有可能是网络原因,也可能是远程服务器不存在此项目
22 | message.i18nmod.cmd_get_langpack.error_timeout=§3[§6调试功能§3] §e下载超时,有可能是网络原因
23 | message.i18nmod.cmd_get_langpack.error_create_folder=§3[§6调试功能§3] §e创建资源包文件夹失败,有可能是已经存在同名文件
24 | message.i18nmod.cmd_get_langpack.error_handle=§3[§6调试功能§3] §e处理文件失败
25 |
26 | message.i18nmod.cmd_upload.empty=§e缺失参数,正确输入应当为 §6/lang_upload <模组资源id>
27 | message.i18nmod.cmd_upload.not_found=§3[§6调试功能§3] §e未找到资源 id 为 §6%s §e的模组,请确认是否输入错误,或者你修改了自动下载的资源包名称
28 | message.i18nmod.cmd_upload.handle_error=§3[§6调试功能§3] §e预处理出现错误,你是否删除了自动下载的文件?亦或是修改了文件名?
29 | message.i18nmod.cmd_upload.handle_success=§3[§6调试功能§3] §e上传前预处理成功!开始正式上传……
30 | message.i18nmod.cmd_upload.upload_error=§3[§6调试功能§3] §e上传执行出现错误,请向酒石酸菌反馈这个问题
31 |
32 | message.i18nmod.cmd_token.empty=§3[§6调试功能§3] §e缺失参数,正确输入应当为 §6/token
33 | message.i18nmod.cmd_upload.no_token=§3[§6调试功能§3] §e缺失 token,请先使用 §6/token §e记录密匙才可以进行上传
34 | message.i18nmod.cmd_upload.excute_ready=§3[§6调试功能§3] §e已经开始执行上传,等待回应可能需要一两分钟时间……
35 | message.i18nmod.cmd_token.200=§3[§6调试功能§3] §e成功!
36 | message.i18nmod.cmd_token.400=§3[§6调试功能§3] §e参数语法错误!
37 | message.i18nmod.cmd_token.401=§3[§6调试功能§3] §e验证密匙错误,请确认你是否输入正确
38 | message.i18nmod.cmd_token.403=§3[§6调试功能§3] §e禁止访问,请确认你是否输入正确
39 | message.i18nmod.cmd_token.404=§3[§6调试功能§3] §e网页不可用,可能是 weblate 服务器宕机了
40 | message.i18nmod.cmd_token.429=§3[§6调试功能§3] §e请求过多,请稍后再试
41 | message.i18nmod.cmd_token.524=§3[§6调试功能§3] §e你上传了个大文件,服务器处理不过来了,不过不用担心,这说明你已经上传好了
42 | message.i18nmod.cmd_token.other=§3[§6调试功能§3] §e其他类型错误,响应代码为:%d
43 | message.i18nmod.cmd_token.error_write=§3[§6调试功能§3] §e写入失败
44 |
45 | message.i18nmod.cmd_upload.result_title=§r§l===== §e§l上传统计 §r§l=====
46 | message.i18nmod.cmd_upload.result_count=§6§l本地总数:%d
47 | message.i18nmod.cmd_upload.result_total=§6§l上传总数:%d
48 | message.i18nmod.cmd_upload.result_accepted=§6§l接收数量:%d
49 | message.i18nmod.cmd_upload.result_not_found=§6§lWeblate 不存在数量:%d
50 | message.i18nmod.cmd_upload.result_skipped=§6§l跳过数量:%d
51 | message.i18nmod.cmd_upload.uploading=§3[§6调试功能§3] §e上传中,已耗时 %d §e秒……
52 |
--------------------------------------------------------------------------------
/src/main/resources/mcmod.info:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "modid": "i18nmod",
4 | "name": "I18n Update Mod",
5 | "description": "专为更新本地化的模组",
6 | "version": "${version}",
7 | "mcversion": "${mcversion}",
8 | "url": "",
9 | "updateUrl": "",
10 | "authorList": ["CFPAOrg"],
11 | "credits": "所有参与本地化的玩家,还有参与代码编写的 Jack,以及提供了配置文件修改思维的 blay09",
12 | "logoFile": "",
13 | "screenshots": [],
14 | "dependencies": []
15 | }
16 | ]
17 |
--------------------------------------------------------------------------------
/src/main/resources/pack.mcmeta:
--------------------------------------------------------------------------------
1 | {
2 | "pack": {
3 | "description": "i18n mod assets",
4 | "pack_format": 3
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/test/java/TestDownloader.java:
--------------------------------------------------------------------------------
1 | import org.cfpa.i18nupdatemod.download.DownloadWindow;
2 | import org.cfpa.i18nupdatemod.download.FileDownloadManager;
3 | import org.junit.Test;
4 |
5 | import java.io.File;
6 |
7 | public class TestDownloader {
8 | /*
9 | @Test
10 | public void testDownloader() throws Throwable {
11 | String dir = System.getProperty("user.dir");
12 | dir = dir + File.separator + "run" + File.separator + "test";
13 | DownloadManager downloader = new DownloadManager("https://covertdragon.team/test/test.zip", "test.zip", dir);
14 | downloader.start();
15 | while (!downloader.isDone()) {
16 | System.out.println(downloader.getCompletePercentage() * 100 + "% Done");
17 | Thread.sleep(50);
18 | }
19 | }
20 | */
21 | /*
22 | @Test
23 | public void testDownloadWindow() throws InterruptedException {
24 | String dir = System.getProperty("user.dir");
25 | dir = dir + File.separator + "run" + File.separator + "test";
26 | DownloadManager downloader = new DownloadManager("https://covertdragon.team/test/test.zip", "test.zip", dir);
27 | downloader.start();
28 | DownloadWindow handler = new DownloadWindow(downloader);
29 | handler.showWindow();
30 | while (!downloader.isDone()) Thread.sleep(50);
31 | }
32 | */
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/TestNotice.java:
--------------------------------------------------------------------------------
1 | import org.apache.commons.io.IOUtils;
2 | import org.junit.Test;
3 |
4 | import java.net.URL;
5 | import java.nio.charset.StandardCharsets;
6 | import java.util.List;
7 |
8 | public class TestNotice {
9 | /*
10 | @Test
11 | public void testNotice() throws Throwable {
12 | URL url = new URL("http://p985car2i.bkt.clouddn.com/Notice.txt");
13 | StringBuilder sb = new StringBuilder();
14 | List strings = IOUtils.readLines(url.openStream(),StandardCharsets.UTF_8);
15 | strings.forEach(v -> sb.append(v).append('\n'));
16 | System.out.print(sb.toString());
17 | }
18 | */
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/TestUtils.java:
--------------------------------------------------------------------------------
1 | public class TestUtils {
2 | /*
3 | @Test
4 | public void testOnlineCheck() {
5 | Assert.assertEquals(online(), true);
6 | }
7 | */
8 | }
9 |
--------------------------------------------------------------------------------