├── .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 | ![001](https://i.loli.net/2018/06/01/5b113185b27c4.png) 26 | 27 | ## 2. 游戏内反馈功能 28 | 29 | 能够让玩家在游戏内通过快捷键反馈相关错误或者意见,同时提供编辑的翻译界面快速打开: 30 | 31 | - 支持 JEI,玩家鼠标指针指向的物品,摁下快捷键 K(可修改),可以打开反馈界面; 32 | - 反馈界面目前暂时使用腾讯问卷系统,玩家只需要粘贴对应信息,同时填写简单的问题说明即可提交; 33 | - 游戏内支持快速打开 Weblate 对应翻译功能。玩家对着物品摁下快捷键 L,即可快速打开 Weblate 对应翻译词条(需要你拥有 Weblate 账户,同时 Weblate 上面有此模组翻译文件); 34 | - 玩家手持物品输入 `/lang_report` 也可打开反馈界面; 35 | 36 | ![002](https://i.loli.net/2018/06/01/5b113185c11a2.png) 37 | 38 | ## 3. 公告功能 39 | 40 | 能够让玩家在游戏内打开公告,公告获取指定动态服务器内容,发布相关更新消息,或者其他说明: 41 | 42 | - 公告在玩家首次进入存档或者服务器会显示一次; 43 | - 公告是动态的,通过联网来获取数据; 44 | - 可以通过配置文件关闭这个功能,或者自定义按钮,自定义远程公告链接; 45 | - 游戏内输入 `/lang_notice` 命令即可再次打开公告; 46 | 47 | ![001](https://i.loli.net/2018/06/01/5b11301444c80.png) 48 | 49 | 50 | 51 | ## 4. 自定义配置 52 | 53 | 提供了多个配置可供调节,专为整合作者,服务器服主提供。可自定义的配置有如下条目,均需要重启游戏: 54 | 55 | ![002](https://i.loli.net/2018/06/01/5b11301457421.png) 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 | --------------------------------------------------------------------------------