Merge pull request #159 from yushijinhun/develop

Release v1.1.44
This commit is contained in:
Haowei Wen 2022-05-03 21:40:08 +08:00 committed by GitHub
commit ddd5081660
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 357 additions and 105 deletions

View file

@ -77,6 +77,9 @@ Configure Minecraft server with the following JVM parameter:
- Mojang namespace
- Legacy skin API polyfill
-Dauthlibinjector.httpdPort={port}
Sets the port used by the local HTTP server, defaults to 0 (randomly chosen).
-Dauthlibinjector.noShowServerName
Do not show authentication server name in Minecraft menu screen.
By default, authlib-injector alters --versionType parameter to display the authentication server name.
@ -94,4 +97,12 @@ Configure Minecraft server with the following JVM parameter:
* Realms (allowed if the option is disabled)
* Telemetry (turned off if the option is disabled)
* Profanity filter (turned off if the option is disabled)
-Dauthlibinjector.profileKey={default|enabled|disabled}
Whether to enable the profile signing key feature. This feature is introduced in 22w17a, and is used to implement the multiplayer secure chat signing.
If this this feature is enabled, Minecraft will send a POST request to /minecraftservices/player/certificates to retrieve the key pair issued by the authentication server.
It's enabled by default if the authentication server sends feature.enable_profile_key option.
If the profile signing key isn't present, the player will be unable to join servers that enable enforce-secure-profile=true option.
And other players' Minecraft client will log a warning when receiving an unsigned chat message.
```

View file

@ -37,7 +37,7 @@ gradle
需要注意的是, authlib-injector 的日志是不会输出到 Minecraft 服务端/客户端的日志文件中的.
每次启动时日志文件都会被清空. 如果有多个进程使用同一个日志文件, 则只有最早启动的会成功打开日志文件.
每次启动时, 日志文件都会被清空. 如果有多个进程使用同一个日志文件, 则只有最早启动的会成功打开日志文件.
-Dauthlibinjector.mojangNamespace={default|enabled|disabled}
设置是否启用 Mojang 命名空间 (@mojang 后缀).
@ -85,6 +85,9 @@ gradle
- Mojang 命名空间
- 旧式皮肤 API polyfill
-Dauthlibinjector.httpdPort={端口号}
设置内置 HTTP 服务器使用的端口号, 默认为 0 (随机分配).
-Dauthlibinjector.noShowServerName
不要在 Minecraft 主界面展示验证服务器名称.
默认情况下, authlib-injector 通过更改 --versionType 参数来在 Minecraft 主界面显示验证服务器名称, 使用本选项可以禁用该功能.
@ -101,6 +104,14 @@ gradle
* 领域权限 (禁用后默认允许)
* 遥测 (禁用后默认关闭)
* 冒犯性内容过滤 (禁用后默认关闭)
-Dauthlibinjector.profileKey={default|enabled|disabled}
是否启用消息签名密钥对功能, 这一功能在 22w17a 引入, 用于多人游戏中聊天消息的数字签名.
启用此功能后, Minecraft 会向 /minecraftservices/player/certificates 发送 POST 请求, 以获取由验证服务器颁发的密钥对.
此功能需要验证服务器支持, 若验证服务器未设置 feature.enable_profile_key 选项, 则该功能默认禁用.
当缺少消息签名密钥时, 玩家将无法进入设置了 enforce-secure-profile=true 选项的服务器.
而当其他玩家的客户端在收到无有效签名的聊天消息时, 会在日志中记录警告.
```
## 捐助

View file

@ -44,6 +44,7 @@ import java.util.Set;
import java.util.stream.Stream;
import moe.yushi.authlibinjector.httpd.DefaultURLRedirector;
import moe.yushi.authlibinjector.httpd.LegacySkinAPIFilter;
import moe.yushi.authlibinjector.httpd.ProfileKeyFilter;
import moe.yushi.authlibinjector.httpd.AntiFeaturesFilter;
import moe.yushi.authlibinjector.httpd.QueryProfileFilter;
import moe.yushi.authlibinjector.httpd.QueryUUIDsFilter;
@ -248,6 +249,11 @@ public final class AuthlibInjector {
filters.add(new AntiFeaturesFilter());
}
boolean profileKeyDefault = Boolean.TRUE.equals(config.getMeta().get("feature.enable_profile_key"));
if (!Config.profileKey.isEnabled(profileKeyDefault)) {
filters.add(new ProfileKeyFilter());
}
return filters;
}
@ -255,7 +261,7 @@ public final class AuthlibInjector {
URLProcessor urlProcessor = new URLProcessor(createFilters(config), new DefaultURLRedirector(config));
ClassTransformer transformer = new ClassTransformer();
transformer.ignores.addAll(Config.ignoredPackages);
transformer.setIgnores(Config.ignoredPackages);
if (Config.dumpClass) {
transformer.listeners.add(new DumpClassListener(Paths.get("").toAbsolutePath()));

View file

@ -60,7 +60,9 @@ public final class Config {
public static FeatureOption mojangNamespace;
public static FeatureOption legacySkinPolyfill;
public static FeatureOption mojangAntiFeatures;
public static FeatureOption profileKey;
public static boolean noShowServerName;
public static int httpdPort;
private static void initDebugOptions() {
String prop = System.getProperty("authlibinjector.debug");
@ -105,34 +107,6 @@ public final class Config {
"com.sun.",
"sun.",
"net.java.",
"com.google.",
"com.ibm.",
"com.jcraft.jogg.",
"com.jcraft.jorbis.",
"com.oracle.",
"com.paulscode.",
"org.GNOME.",
"org.apache.",
"org.graalvm.",
"org.jcp.",
"org.json.",
"org.lwjgl.",
"org.objectweb.asm.",
"org.w3c.",
"org.xml.",
"org.yaml.snakeyaml.",
"gnu.trove.",
"io.netty.",
"it.unimi.dsi.fastutil.",
"javassist.",
"jline.",
"joptsimple.",
"oracle.",
"oshi.",
"paulscode.",
};
private static void initIgnoredPackages() {
@ -204,7 +178,9 @@ public final class Config {
mojangNamespace = parseFeatureOption("authlibinjector.mojangNamespace");
legacySkinPolyfill = parseFeatureOption("authlibinjector.legacySkinPolyfill");
mojangAntiFeatures = parseFeatureOption("authlibinjector.mojangAntiFeatures");
profileKey = parseFeatureOption("authlibinjector.profileKey");
httpdDisabled = System.getProperty("authlibinjector.disableHttpd") != null;
noShowServerName = System.getProperty("authlibinjector.noShowServerName") != null;
httpdPort = Integer.getInteger("authlibinjector.httpdPort", 0);
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2022 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package moe.yushi.authlibinjector.httpd;
import static moe.yushi.authlibinjector.util.IOUtils.CONTENT_TYPE_JSON;
import moe.yushi.authlibinjector.AuthlibInjector;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Response;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Status;
import moe.yushi.authlibinjector.internal.org.json.simple.JSONObject;
import moe.yushi.authlibinjector.transform.PerformanceMetrics;
/**
* Authlib-injector's debug API
*/
public class DebugApiEndpoint {
public Response serve(IHTTPSession session) {
if (session.getUri().equals("/debug/metrics") && session.getMethod().equals("GET")) {
PerformanceMetrics metrics = AuthlibInjector.getClassTransformer().performanceMetrics;
JSONObject response = new JSONObject();
response.put("totalTime", metrics.getTotalTime());
response.put("matchTime", metrics.getMatchTime());
response.put("scanTime", metrics.getScanTime());
response.put("analysisTime", metrics.getAnalysisTime());
response.put("classesScanned", metrics.getClassesScanned());
response.put("classesSkipped", metrics.getClassesSkipped());
return Response.newFixedLength(Status.OK, CONTENT_TYPE_JSON, response.toJSONString());
} else {
return Response.newFixedLength(Status.NOT_FOUND, null, null);
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2022 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package moe.yushi.authlibinjector.httpd;
import java.io.IOException;
import java.util.Optional;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Response;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.Status;
/**
* Intercepts Minecraft's request to https://api.minecraftservices.com/player/certificates,
* and returns an empty response.
*/
public class ProfileKeyFilter implements URLFilter {
@Override
public boolean canHandle(String domain) {
return domain.equals("api.minecraftservices.com");
}
@Override
public Optional<Response> handle(String domain, String path, IHTTPSession session) throws IOException {
if (domain.equals("api.minecraftservices.com") && path.equals("/player/certificates") && session.getMethod().equals("POST")) {
return Optional.of(Response.newFixedLength(Status.NO_CONTENT, null, null));
}
return Optional.empty();
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2022 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -37,6 +37,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import moe.yushi.authlibinjector.Config;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IHTTPSession;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.IStatus;
import moe.yushi.authlibinjector.internal.fi.iki.elonen.NanoHTTPD;
@ -67,6 +68,10 @@ public class URLProcessor {
* @return the transformed URL, or empty if it doesn't need to be transformed
*/
public Optional<String> transformURL(String inputUrl) {
if (!inputUrl.startsWith("http")) {
// fast path
return Optional.empty();
}
Matcher matcher = URL_REGEX.matcher(inputUrl);
if (!matcher.find()) {
return Optional.empty();
@ -98,6 +103,7 @@ public class URLProcessor {
return redirector.redirect(domain, path);
}
private DebugApiEndpoint debugApi = new DebugApiEndpoint();
private volatile NanoHTTPD httpd;
private final Object httpdLock = new Object();
@ -117,9 +123,13 @@ public class URLProcessor {
}
private NanoHTTPD createHttpd() {
return new NanoHTTPD("127.0.0.1", 0) {
return new NanoHTTPD("127.0.0.1", Config.httpdPort) {
@Override
public Response serve(IHTTPSession session) {
if (session.getUri().startsWith("/debug/")) {
return debugApi.serve(session);
}
Matcher matcher = LOCAL_URL_REGEX.matcher(session.getUri());
if (matcher.find()) {
String protocol = matcher.group("protocol");

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2022 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -27,11 +27,10 @@ import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
@ -43,58 +42,59 @@ public class ClassTransformer implements ClassFileTransformer {
public final List<TransformUnit> units = new CopyOnWriteArrayList<>();
public final List<ClassLoadingListener> listeners = new CopyOnWriteArrayList<>();
public final Set<String> ignores = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final PerformanceMetrics performanceMetrics = new PerformanceMetrics();
private String[] ignores = new String[0];
private static class TransformContextImpl implements TransformContext {
private class TransformHandle {
private final String className;
private class TransformContextImpl implements TransformContext {
public boolean isInterface;
public boolean modifiedMark;
public int minVersionMark = -1;
public int upgradedVersionMark = -1;
public boolean callbackMetafactoryRequested = false;
public boolean modifiedMark;
public int minVersionMark = -1;
public int upgradedVersionMark = -1;
public boolean callbackMetafactoryRequested = false;
public TransformContextImpl(String className) {
this.className = className;
}
@Override
public void markModified() {
modifiedMark = true;
}
@Override
public void markModified() {
modifiedMark = true;
}
@Override
public void requireMinimumClassVersion(int version) {
if (this.minVersionMark < version) {
this.minVersionMark = version;
}
}
@Override
public void requireMinimumClassVersion(int version) {
if (this.minVersionMark < version) {
this.minVersionMark = version;
@Override
public void upgradeClassVersion(int version) {
if (this.upgradedVersionMark < version) {
this.upgradedVersionMark = version;
}
}
@Override
public Handle acquireCallbackMetafactory() {
this.callbackMetafactoryRequested = true;
return new Handle(
H_INVOKESTATIC,
className.replace('.', '/'),
CallbackSupport.METAFACTORY_NAME,
CallbackSupport.METAFACTORY_SIGNATURE,
TransformHandle.this.isInterface());
}
@Override
public List<String> getStringConstants() {
return TransformHandle.this.getStringConstants();
}
}
@Override
public void upgradeClassVersion(int version) {
if (this.upgradedVersionMark < version) {
this.upgradedVersionMark = version;
}
}
@Override
public Handle acquireCallbackMetafactory() {
this.callbackMetafactoryRequested = true;
return new Handle(
H_INVOKESTATIC,
className.replace('.', '/'),
CallbackSupport.METAFACTORY_NAME,
CallbackSupport.METAFACTORY_SIGNATURE,
isInterface);
}
}
private static class TransformHandle {
private final String className;
private final ClassLoader classLoader;
private byte[] classBuffer;
private ClassReader cachedClassReader;
private List<String> cachedConstants;
private List<TransformUnit> appliedTransformers;
private int minVersion = -1;
@ -107,30 +107,82 @@ public class ClassTransformer implements ClassFileTransformer {
this.classLoader = classLoader;
}
public void accept(TransformUnit unit) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
TransformContextImpl ctx = new TransformContextImpl(className);
private ClassReader getClassReader() {
if (cachedClassReader == null)
cachedClassReader = new ClassReader(classBuffer);
return cachedClassReader;
}
Optional<ClassVisitor> optionalVisitor = unit.transform(classLoader, className, writer, ctx);
if (optionalVisitor.isPresent()) {
ClassReader reader = new ClassReader(classBuffer);
ctx.isInterface = (reader.getAccess() & ACC_INTERFACE) != 0;
reader.accept(optionalVisitor.get(), 0);
if (ctx.modifiedMark) {
log(INFO, "Transformed [" + className + "] with [" + unit + "]");
if (appliedTransformers == null) {
appliedTransformers = new ArrayList<>();
}
appliedTransformers.add(unit);
classBuffer = writer.toByteArray();
if (ctx.minVersionMark > this.minVersion) {
this.minVersion = ctx.minVersionMark;
}
if (ctx.upgradedVersionMark > this.upgradedVersion) {
this.upgradedVersion = ctx.upgradedVersionMark;
}
this.addCallbackMetafactory |= ctx.callbackMetafactoryRequested;
private boolean isInterface() {
return (getClassReader().getAccess() & ACC_INTERFACE) != 0;
}
private List<String> getStringConstants() {
if (cachedConstants == null)
cachedConstants = extractStringConstants(getClassReader());
return cachedConstants;
}
public void accept(TransformUnit... units) {
long t0 = System.nanoTime();
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
TransformContextImpl[] ctxs = new TransformContextImpl[units.length];
ClassVisitor chain = writer;
for (int i = units.length - 1; i >= 0; i--) {
TransformContextImpl ctx = new TransformContextImpl();
Optional<ClassVisitor> visitor = units[i].transform(classLoader, className, chain, ctx);
if (!visitor.isPresent())
continue;
ctxs[i] = ctx;
chain = visitor.get();
}
long t1 = System.nanoTime();
synchronized (performanceMetrics) {
performanceMetrics.scanTime += t1 - t0;
}
if (chain == writer)
return;
t0 = System.nanoTime();
getClassReader().accept(chain, 0);
t1 = System.nanoTime();
synchronized (performanceMetrics) {
performanceMetrics.analysisTime += t1 - t0;
}
boolean modified = false;
for (int i = 0; i < units.length; i++) {
TransformContextImpl ctx = ctxs[i];
if (ctx == null || !ctx.modifiedMark)
continue;
log(INFO, "Transformed [" + className + "] with [" + units[i] + "]");
if (appliedTransformers == null)
appliedTransformers = new ArrayList<>();
appliedTransformers.add(units[i]);
if (ctx.minVersionMark > this.minVersion) {
this.minVersion = ctx.minVersionMark;
}
if (ctx.upgradedVersionMark > this.upgradedVersion) {
this.upgradedVersion = ctx.upgradedVersionMark;
}
this.addCallbackMetafactory |= ctx.callbackMetafactoryRequested;
modified = true;
}
if (modified) {
classBuffer = writer.toByteArray();
cachedClassReader = null;
cachedConstants = null;
}
}
@ -168,22 +220,42 @@ public class ClassTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String internalClassName, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (internalClassName != null && classfileBuffer != null) {
try {
long t0 = System.nanoTime();
String className = internalClassName.replace('/', '.');
for (String prefix : ignores) {
if (className.startsWith(prefix)) {
for (String ignore : ignores) {
if (className.startsWith(ignore)) {
listeners.forEach(it -> it.onClassLoading(loader, className, classfileBuffer, Collections.emptyList()));
long t1 = System.nanoTime();
synchronized (performanceMetrics) {
performanceMetrics.classesSkipped++;
performanceMetrics.totalTime += t1 - t0;
performanceMetrics.matchTime += t1 - t0;
}
return null;
}
}
long t1 = System.nanoTime();
TransformHandle handle = new TransformHandle(loader, className, classfileBuffer);
units.forEach(handle::accept);
TransformUnit[] unitsArray = units.toArray(new TransformUnit[0]);
handle.accept(unitsArray);
listeners.forEach(it -> it.onClassLoading(loader, className, handle.getFinalResult(), handle.getAppliedTransformers()));
Optional<byte[]> transformResult = handle.finish();
if (Config.printUntransformedClass && !transformResult.isPresent()) {
log(DEBUG, "No transformation is applied to [" + className + "]");
}
long t2 = System.nanoTime();
synchronized (performanceMetrics) {
performanceMetrics.classesScanned++;
performanceMetrics.totalTime += t2 - t0;
performanceMetrics.matchTime += t1 - t0;
}
return transformResult.orElse(null);
} catch (Throwable e) {
log(WARNING, "Failed to transform [" + internalClassName + "]", e);
@ -191,4 +263,25 @@ public class ClassTransformer implements ClassFileTransformer {
}
return null;
}
private static List<String> extractStringConstants(ClassReader reader) {
List<String> constants = new ArrayList<>();
int constantPoolSize = reader.getItemCount();
char[] buf = new char[reader.getMaxStringLength()];
for (int idx = 1; idx < constantPoolSize; idx++) {
int offset = reader.getItem(idx);
if (offset == 0)
continue;
int type = reader.readByte(offset - 1);
if (type == 8) { // CONSTANT_String_info
String constant = (String) reader.readConst(idx, buf);
constants.add(constant);
}
}
return constants;
}
public void setIgnores(Collection<String> newIgnores) {
ignores = newIgnores.toArray(ignores);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2022 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -26,6 +26,17 @@ public abstract class LdcTransformUnit implements TransformUnit {
@Override
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
boolean matched = false;
for (String constant : ctx.getStringConstants()) {
Optional<String> transformed = transformLdc(constant);
if (transformed.isPresent() && !transformed.get().equals(constant)) {
matched = true;
break;
}
}
if (!matched)
return Optional.empty();
return Optional.of(new ClassVisitor(ASM9, writer) {
@Override

View file

@ -0,0 +1,34 @@
/*
* Copyright (C) 2022 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package moe.yushi.authlibinjector.transform;
public class PerformanceMetrics {
volatile long totalTime;
volatile long matchTime;
volatile long scanTime;
volatile long analysisTime;
volatile long classesScanned;
volatile long classesSkipped;
public synchronized long getTotalTime() { return totalTime; }
public synchronized long getMatchTime() { return matchTime; }
public synchronized long getScanTime() { return scanTime; }
public synchronized long getAnalysisTime() { return analysisTime; }
public synchronized long getClassesScanned() { return classesScanned; }
public synchronized long getClassesSkipped() { return classesSkipped; }
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2022 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -16,6 +16,7 @@
*/
package moe.yushi.authlibinjector.transform;
import java.util.List;
import org.objectweb.asm.Handle;
public interface TransformContext {
@ -27,4 +28,6 @@ public interface TransformContext {
void upgradeClassVersion(int version);
Handle acquireCallbackMetafactory();
List<String> getStringConstants();
}

View file

@ -31,6 +31,10 @@ public class UsernameCharacterCheckTransformer implements TransformUnit {
@Override
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext context) {
if (!context.getStringConstants().contains("Invalid characters in username")) {
return Optional.empty();
}
return Optional.of(new ClassVisitor(ASM9, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2022 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -29,11 +29,14 @@ import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.regex.Pattern;
import moe.yushi.authlibinjector.Config;
public final class Logging {
private Logging() {}
private static final Pattern CONTROL_CHARACTERS_FILTER = Pattern.compile("[\\p{Cc}&&[^\r\n\t]]");
private static final PrintStream out = System.err;
private static final FileChannel logfile = openLogFile();
@ -83,13 +86,12 @@ public final class Logging {
log += sw.toString();
}
// remove control characters to prevent messing up the console
log = log.replaceAll("[\\p{Cc}&&[^\r\n\t]]", "");
log = CONTROL_CHARACTERS_FILTER.matcher(log).replaceAll("");
out.println(log);
if (logfile != null) {
try {
logfile.write(Charset.defaultCharset().encode(log + System.lineSeparator()));
logfile.force(true);
} catch (IOException ex) {
out.println("[authlib-injector] [ERROR] Error writing to log file: " + ex);
}