在使用夸克MOD时,经常会在后台看到这样的报错:“Failed to load patreon information”。
根据详细报错栈和见名知义的命名以及开源代码,可以定位到是夸克MOD尝试加载赞助者信息时因为国内无法访问"https://raw.githubusercontent.com/Vazkii/Quark/master/contributors.properties"导致的。
实际上这不是BUG,曾经多次有人向作者提出issue,但是作者都在无回复且没有任何ref的情况下直接关闭并且拒绝提供关闭打印此报错的开关。
https://github.com/VazkiiMods/Quark/issues/3160
https://github.com/VazkiiMods/Quark/issues/3331
https://github.com/VazkiiMods/Quark/issues/3416
在使用了一些工具自动监测服务器异常栈的时候,这个错误导致服务器没完没了的误警报,严重干扰运维。
解决方法1:修改hosts
raw.githubusercontent.com的IP地址是
185.199.108.133
185.199.109.133
185.199.110.133
185.199.111.133
但是可以肯定的是,如果hosts能解决问题你就不会来看我的专栏了。
分析代码
从git下载代码,找到vazkii.quark.base.handler.ContributorRewardHandler。可以根据报错栈定位到,实际发生错误的,产生网络请求的代码位于vazkii.quark.base.handler.ContributorRewardHandler.ThreadContributorListLoader中。
private static class ThreadContributorListLoader extends Thread {
public ThreadContributorListLoader() {
setName("Quark Contributor Loading Thread");
setDaemon(true);
start();
}
@Override
public void run() {
try {
URL url = new URL("https://raw.githubusercontent.com/Vazkii/Quark/master/contributors.properties");
URLConnection conn = url.openConnection();
conn.setConnectTimeout(10*1000);
conn.setReadTimeout(10*1000);
Properties patreonTiers = new Properties();
try (InputStreamReader reader = new InputStreamReader(conn.getInputStream())) {
patreonTiers.load(reader);
load(patreonTiers);
}
} catch (IOException e) {
Quark.LOG.error("Failed to load patreon information", e);
}
}
} 罪魁祸首就是"Quark.LOG.error("Failed to load patreon information", e);"这句代码导致每次同步失败都会产生错误栈打印。
修改代码建议使用Idea(https://www.jetbrains.com/idea/);
删除指的是将代码注释掉(Java语言是//);
解决方法2:提供Http-Proxy助其能访问
此方法是最符合人类行为道德的解决办法,再请求的代码中添加代理功能并搭建代理即可,注意将InetSocketAddress修改为你实际的http-proxy地址。
SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 10809);
Proxy proxy = new Proxy(Proxy.Type.HTTP, socketAddress);
URL url = new URL("https://raw.githubusercontent.com/Vazkii/Quark/master/contributors.properties");
URLConnection conn = url.openConnection(proxy); 之后使用build构建mod。
解决方法3:删除日志打印语句
不是所有人所有服务器都有能力在宿主搭建代理(比如MC主机出租,只能选择启动/关闭/重启,不能随意运行其他软件),所以这里提供一种不再打印错误日志的修复办法。
在vazkii.quark.base.handler.ContributorRewardHandler.ThreadContributorListLoader#run方法中,删除掉Quark.LOG.error("Failed to load patreon information", e);即可屏蔽掉日志输出,就算实际执行的代码闹翻天,控制台也会无事发生。
@Override
public void run() {
try {
URL url = new URL("https://raw.githubusercontent.com/Vazkii/Quark/master/contributors.properties");
URLConnection conn = url.openConnection();
conn.setConnectTimeout(10*1000);
conn.setReadTimeout(10*1000);
Properties patreonTiers = new Properties();
try (InputStreamReader reader = new InputStreamReader(conn.getInputStream())) {
patreonTiers.load(reader);
load(patreonTiers);
}
} catch (IOException e) {
// Quark.LOG.error("Failed to load patreon information", e);
}
} 之后使用build构建mod。
解决方法4:关闭检查功能
作为程序员和运维,其实是不能容忍跑不通没有意义的代码在后台不停的报错的。所以这里提供另一种严重违反道德的做法,即禁用赞助者信息更新。
这个方法可能会在MOD更新后失效,毕竟代码随时可能会变。
初始化赞助者更新的方法为vazkii.quark.base.handler.ContributorRewardHandler#init(实际上把init方法体内的所有东西删除就可以了,但是作为程序员的极度强迫症,一定要清理干净所有调用才舒服。)
public static void init() {
if (thread != null && thread.isAlive())
return;
thread = new ThreadContributorListLoader();
} 此方法有两个引用:
1:玩家加入服务器时
vazkii.quark.base.handler.ContributorRewardHandler#onPlayerJoin
@SubscribeEvent
@OnlyIn(Dist.DEDICATED_SERVER)
public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
ContributorRewardHandler.init();
} 注释掉注解@SubscribeEvent和@OnlyIn(Dist.DEDICATED_SERVER)或者直接把这个方法给扬了,即可不再向Forge注册玩家加入的事件。
2:MOD启动时vazkii.quark.base.proxy.CommonProxy#setup将最后initContributorRewards的调用注释掉
public void setup(FMLCommonSetupEvent event) {
QuarkNetwork.setup();
BrewingHandler.setup();
CapabilityHandler.setup();
JigsawRegistryHelper.setup();
ModuleLoader.INSTANCE.setup(event);
initContributorRewards();
} 3:MOD在客户端启动时vazkii.quark.base.proxy.ClientProxy#initContributorRewards将最后super.initContributorRewards的调用注释掉
@Override
protected void initContributorRewards() {
ContributorRewardHandler.getLocalName();
super.initContributorRewards();
} 之后使用build构建mod。
解决方法4:直接编辑字节码删除打印日志的语句
由于网络问题在国内编译MOD会耗费巨大的时间而且99%的情况下会因为网络问题失败。导致方法2,3都不可用(比如我使用500M的国际CN2线路都需要10~20分钟才能同步下来各种依赖和库,如果直接使用直连网络,下载100M的gradle就需要1个小时了)。
直接删除打印报错日志的那句代码是更快捷的方法,基于字节码修改工具虽然可以反编译修改原始代码,但是二次从代码编译为新的字节码是需要依赖库的,所以只能直接修改字节码,因为字节码过于复杂过于底层,所以添加内容是极度困难的,故采用删除日志打印语句的解决方法三。
这里使用了Recaf这个工具。MOD文件使用Quark-r2.4-319.jar之后的版本肯定会有各种更新,所以需要自行判断删除哪些代码,如何修改GOTO语句到哪个位置(要不怎么会把这个“简单”的方法排在后边)。
1:使用Recaf打开下载的jar文件,定位到类;
2:右键tab更改为表格视图,右键代码进入ASM编辑模式;

打开ASM编辑
3:找到打印日志的那句话

找到调用Logger打印的语句
4:修改字节码

修改字节码
删除掉Y:对应位置原来的代码,从"GETSTATIC" 到 "INVOKEINTERFACE",修改为GOTO Z,保存,导出jar。
含义是(简单理解):Z对应的代码为RETURN 即方法的"}",本来的代码执行Z之前会执行Y,即打印日志(调用Logger.error),将Y直接修改为GOTO即相当于删除本句代码并且不会导致代码行号重排,避免了大幅度修改字节码引起的各种问题(大规模增加代码比如使用解决方法二,将会涉及修改常量池、引入新的类、类索引、修改异常表、修复由于修改导致的序号重排等纯手动高难度操作,手动修改几乎必出错)。
彩蛋:实际上这样修改的代码再次编译后会变成这样:
如果只看反编译的代码是很正常的,去掉了打印的代码,使其变成ignore。

二次编译后反编译
实际上字节码会变成这个比较鬼畜的样子(X从GOTO Z变成GOTO Y,Y从GOTO Z变成RETURN,Z直接空了)。

二次编译后的字节码