mirror of
https://github.com/chylex/Minecraft-Window-Title.git
synced 2025-05-24 14:34:12 +02:00
Implement custom window title w/ {mcversion} and {modversion} tokens
This commit is contained in:
parent
abaa23c5c2
commit
c6102427aa
Fabric/src/main
Forge/src/main
java/chylex/customwindowtitle
resources
@ -0,0 +1,46 @@
|
|||||||
|
package chylex.customwindowtitle;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class TitleParser{
|
||||||
|
private static final Pattern tokenRegex = Pattern.compile("\\{([a-z]+)(?::([^}]+))?}");
|
||||||
|
private static final Logger logger = LogManager.getLogger("CustomWindowTitle");
|
||||||
|
|
||||||
|
public static String parse(String input){
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
Matcher matcher = tokenRegex.matcher(input);
|
||||||
|
|
||||||
|
while(matcher.find()){
|
||||||
|
String token = matcher.group(1);
|
||||||
|
String[] args = StringUtils.split(matcher.group(2), ',');
|
||||||
|
|
||||||
|
String result = null;
|
||||||
|
|
||||||
|
try{
|
||||||
|
result = TitleTokens.getTokenFunction(token).apply(args == null ? ArrayUtils.EMPTY_STRING_ARRAY : args);
|
||||||
|
}catch(TokenException e){
|
||||||
|
logger.warn("Error processing token '" + token + "': " + e.getMessage());
|
||||||
|
}catch(Throwable t){
|
||||||
|
logger.warn("Error processing token '" + token + "': " + t.getMessage(), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null){
|
||||||
|
matcher.appendReplacement(buffer, input.substring(matcher.start(), matcher.end()));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
matcher.appendReplacement(buffer, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher.appendTail(buffer);
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static class
|
||||||
|
|
||||||
|
private TitleParser(){}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package chylex.customwindowtitle;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public final class TitleTokens{
|
||||||
|
|
||||||
|
// Registry
|
||||||
|
|
||||||
|
private static final Map<String, Function<String[], String>> tokenMap = new HashMap<>();
|
||||||
|
|
||||||
|
public static void registerToken(String token, Function<String[], String> processor){
|
||||||
|
tokenMap.putIfAbsent(token, processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Function<String[], String> getTokenFunction(String token){
|
||||||
|
return tokenMap.getOrDefault(token, args -> null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arguments
|
||||||
|
|
||||||
|
public static Function<String[], String> noArgs(Supplier<String> func){
|
||||||
|
return args -> args.length > 0 ? fail("expected no arguments, got " + args.length) : func.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Function<String[], String> oneArg(UnaryOperator<String> func){
|
||||||
|
return args -> args.length != 1 ? fail("expected 1 argument, got " + args.length) : func.apply(args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Function<String[], String> rangeArgs(int min, int max, Function<String[], String> func){
|
||||||
|
return args -> args.length < min || args.length > max ? fail("expected between " + min + " and " + max + " arguments, got " + args.length) : func.apply(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fail(String message){
|
||||||
|
throw new TokenException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static class
|
||||||
|
|
||||||
|
private TitleTokens(){}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package chylex.customwindowtitle;
|
||||||
|
|
||||||
|
public class TokenException extends RuntimeException{
|
||||||
|
public TokenException(String message){
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package chylex.customwindowtitle.fabric;
|
||||||
|
import chylex.customwindowtitle.TitleParser;
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class CustomWindowTitle implements ClientModInitializer{
|
||||||
|
private static final String defaultTitle = "Minecraft {mcversion}";
|
||||||
|
private String configTitle;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient(){
|
||||||
|
Path configFile = Paths.get(FabricLoader.getInstance().getConfigDirectory().getAbsolutePath(), "customwindowtitle-client.toml");
|
||||||
|
|
||||||
|
try{
|
||||||
|
String prefix = "title = ";
|
||||||
|
|
||||||
|
if (!Files.exists(configFile)){
|
||||||
|
Files.write(configFile, Collections.singletonList(prefix + '"' + defaultTitle + '"'), StandardCharsets.UTF_8);
|
||||||
|
configTitle = defaultTitle;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
configTitle = Files
|
||||||
|
.readAllLines(configFile, StandardCharsets.UTF_8)
|
||||||
|
.stream()
|
||||||
|
.filter(line -> line.startsWith(prefix))
|
||||||
|
.map(line -> StringUtils.strip(StringUtils.removeStart(line, prefix).trim(), "\""))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(defaultTitle);
|
||||||
|
}
|
||||||
|
}catch(IOException e){
|
||||||
|
throw new RuntimeException("CustomWindowTitle configuration error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenData.register();
|
||||||
|
MinecraftClient.getInstance().execute(this::updateTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTitle(){
|
||||||
|
MinecraftClient.getInstance().getWindow().setTitle(TitleParser.parse(configTitle));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package chylex.customwindowtitle.fabric;
|
||||||
|
import chylex.customwindowtitle.TokenException;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.minecraft.SharedConstants;
|
||||||
|
import static chylex.customwindowtitle.TitleTokens.noArgs;
|
||||||
|
import static chylex.customwindowtitle.TitleTokens.oneArg;
|
||||||
|
import static chylex.customwindowtitle.TitleTokens.registerToken;
|
||||||
|
|
||||||
|
final class TokenData{
|
||||||
|
static void register(){
|
||||||
|
registerToken("mcversion", noArgs(TokenData::getMinecraftVersion));
|
||||||
|
registerToken("modversion", oneArg(TokenData::getModVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getMinecraftVersion(){
|
||||||
|
return SharedConstants.getGameVersion().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getModVersion(String modId){
|
||||||
|
return FabricLoader.getInstance().getModContainer(modId).orElseThrow(() -> new TokenException("mod info for '" + modId + "' not found")).getMetadata().getVersion().getFriendlyString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package chylex.customwindowtitle.fabric.mixin;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(MinecraftClient.class)
|
||||||
|
public final class DisableVanillaTitle{
|
||||||
|
@Inject(method = "updateWindowTitle()V", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void updateTitle(CallbackInfo info){
|
||||||
|
info.cancel();
|
||||||
|
}
|
||||||
|
}
|
13
Fabric/src/main/resources/mixins.json
Normal file
13
Fabric/src/main/resources/mixins.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"package": "chylex.customwindowtitle.fabric.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_8",
|
||||||
|
"mixins": [
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
"DisableVanillaTitle"
|
||||||
|
],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package chylex.customwindowtitle;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class TitleParser{
|
||||||
|
private static final Pattern tokenRegex = Pattern.compile("\\{([a-z]+)(?::([^}]+))?}");
|
||||||
|
private static final Logger logger = LogManager.getLogger("CustomWindowTitle");
|
||||||
|
|
||||||
|
public static String parse(String input){
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
Matcher matcher = tokenRegex.matcher(input);
|
||||||
|
|
||||||
|
while(matcher.find()){
|
||||||
|
String token = matcher.group(1);
|
||||||
|
String[] args = StringUtils.split(matcher.group(2), ',');
|
||||||
|
|
||||||
|
String result = null;
|
||||||
|
|
||||||
|
try{
|
||||||
|
result = TitleTokens.getTokenFunction(token).apply(args == null ? ArrayUtils.EMPTY_STRING_ARRAY : args);
|
||||||
|
}catch(TokenException e){
|
||||||
|
logger.warn("Error processing token '" + token + "': " + e.getMessage());
|
||||||
|
}catch(Throwable t){
|
||||||
|
logger.warn("Error processing token '" + token + "': " + t.getMessage(), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null){
|
||||||
|
matcher.appendReplacement(buffer, input.substring(matcher.start(), matcher.end()));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
matcher.appendReplacement(buffer, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher.appendTail(buffer);
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static class
|
||||||
|
|
||||||
|
private TitleParser(){}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package chylex.customwindowtitle;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public final class TitleTokens{
|
||||||
|
|
||||||
|
// Registry
|
||||||
|
|
||||||
|
private static final Map<String, Function<String[], String>> tokenMap = new HashMap<>();
|
||||||
|
|
||||||
|
public static void registerToken(String token, Function<String[], String> processor){
|
||||||
|
tokenMap.putIfAbsent(token, processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Function<String[], String> getTokenFunction(String token){
|
||||||
|
return tokenMap.getOrDefault(token, args -> null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arguments
|
||||||
|
|
||||||
|
public static Function<String[], String> noArgs(Supplier<String> func){
|
||||||
|
return args -> args.length > 0 ? fail("expected no arguments, got " + args.length) : func.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Function<String[], String> oneArg(UnaryOperator<String> func){
|
||||||
|
return args -> args.length != 1 ? fail("expected 1 argument, got " + args.length) : func.apply(args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Function<String[], String> rangeArgs(int min, int max, Function<String[], String> func){
|
||||||
|
return args -> args.length < min || args.length > max ? fail("expected between " + min + " and " + max + " arguments, got " + args.length) : func.apply(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fail(String message){
|
||||||
|
throw new TokenException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static class
|
||||||
|
|
||||||
|
private TitleTokens(){}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package chylex.customwindowtitle;
|
||||||
|
|
||||||
|
public class TokenException extends RuntimeException{
|
||||||
|
public TokenException(String message){
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package chylex.customwindowtitle.forge;
|
||||||
|
import chylex.customwindowtitle.TitleParser;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraftforge.common.ForgeConfigSpec;
|
||||||
|
import net.minecraftforge.common.ForgeConfigSpec.ConfigValue;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.ModLoadingContext;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
import net.minecraftforge.fml.config.ModConfig.Type;
|
||||||
|
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||||
|
|
||||||
|
@Mod("customwindowtitle")
|
||||||
|
public class CustomWindowTitle{
|
||||||
|
private final ConfigValue<String> configTitle;
|
||||||
|
|
||||||
|
public CustomWindowTitle(){
|
||||||
|
ForgeConfigSpec.Builder configBuilder = new ForgeConfigSpec.Builder();
|
||||||
|
|
||||||
|
configTitle = configBuilder.define("title", "Minecraft {mcversion}");
|
||||||
|
|
||||||
|
ModLoadingContext.get().registerConfig(Type.CLIENT, configBuilder.build());
|
||||||
|
FMLJavaModLoadingContext.get().getModEventBus().register(this);
|
||||||
|
|
||||||
|
TokenData.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public void onClientSetup(FMLClientSetupEvent e){
|
||||||
|
e.getMinecraftSupplier().get().execute(this::updateTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTitle(){
|
||||||
|
Minecraft.getInstance().getMainWindow().func_230148_b_(TitleParser.parse(configTitle.get()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package chylex.customwindowtitle.forge;
|
||||||
|
import chylex.customwindowtitle.TokenException;
|
||||||
|
import net.minecraft.util.SharedConstants;
|
||||||
|
import net.minecraftforge.fml.ModList;
|
||||||
|
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||||
|
import net.minecraftforge.forgespi.language.IModInfo;
|
||||||
|
import static chylex.customwindowtitle.TitleTokens.noArgs;
|
||||||
|
import static chylex.customwindowtitle.TitleTokens.oneArg;
|
||||||
|
import static chylex.customwindowtitle.TitleTokens.registerToken;
|
||||||
|
|
||||||
|
final class TokenData{
|
||||||
|
static void register(){
|
||||||
|
registerToken("mcversion", noArgs(TokenData::getMinecraftVersion));
|
||||||
|
registerToken("modversion", oneArg(TokenData::getModVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getMinecraftVersion(){
|
||||||
|
return SharedConstants.getVersion().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getModVersion(String modId){
|
||||||
|
ModFileInfo file = ModList.get().getModFileById(modId);
|
||||||
|
|
||||||
|
if (file == null){
|
||||||
|
throw new TokenException("mod file for '" + modId + "' not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
for(IModInfo info : file.getMods()){
|
||||||
|
if (info.getModId().equals(modId)){
|
||||||
|
return info.getVersion().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TokenException("mod info for '" + modId + "' not found");
|
||||||
|
}
|
||||||
|
}
|
3
Forge/src/main/resources/META-INF/coremods.json
Normal file
3
Forge/src/main/resources/META-INF/coremods.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"CustomWindowTitle": "coremods/main.js"
|
||||||
|
}
|
19
Forge/src/main/resources/coremods/main.js
Normal file
19
Forge/src/main/resources/coremods/main.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
function initializeCoreMod(){
|
||||||
|
var opcodes = Java.type("org.objectweb.asm.Opcodes");
|
||||||
|
var InsnNode = Java.type("org.objectweb.asm.tree.InsnNode");
|
||||||
|
|
||||||
|
return {
|
||||||
|
"CustomWindowTitle": {
|
||||||
|
"target": {
|
||||||
|
"type": "METHOD",
|
||||||
|
"class": "net.minecraft.client.Minecraft",
|
||||||
|
"methodName": "func_230150_b_",
|
||||||
|
"methodDesc": "()V"
|
||||||
|
},
|
||||||
|
"transformer": function(methodNode){
|
||||||
|
methodNode.instructions.insert(new InsnNode(opcodes.RETURN));
|
||||||
|
return methodNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user