10 Commits

Author SHA1 Message Date
TheWhiteDog9487 1593b3f86f 🪛 chore: 将 .kotlin 文件夹加入忽略列表 2026-09-16 16:24:15 +08:00
TheWhiteDog9487 dc2b661d51 🛠️ optimization: 将配置界面的选项拆分成不同的类别 2026-09-16 16:19:43 +08:00
TheWhiteDog9487 2882def6bc 🧹 cleanup: 在调用栈搜索算法稳定后,不再需要向外部提供开关 2026-09-16 15:59:09 +08:00
TheWhiteDog9487 3de1313f9d 🪲 fix: 解决在删除IsFrameworkClass内部兼容代码之后嵌套的第三方Mod无法获取文件路径从而导致游戏崩溃的Bug 2026-09-16 15:59:09 +08:00
TheWhiteDog9487 106f531f0b 🪲 fix: 将忘记修改的 special_provider_UNKNOWN 重命名为正确的 special_string_UNKNOWN 2026-09-16 15:59:09 +08:00
TheWhiteDog9487 16b1a01255 🧩 feat: 调整匹配规则优先级,(有望彻底)解决误报问题 2026-09-16 15:35:57 +08:00
TheWhiteDog9487 6f0f58a715 🧹 cleanup: 清理代码 2026-09-15 11:39:18 +08:00
TheWhiteDog9487 bce136188a 🧩 feat: 支持显示Mod文件路径 2026-09-15 11:33:48 +08:00
TheWhiteDog9487 863bcfd0e2 🧩 feat: 支持从线程调用栈进行强力搜索 2026-09-15 11:31:32 +08:00
TheWhiteDog9487 e0af7c05eb 📖 docs: 修复英文README图片不显示的问题 2026-09-07 10:34:09 +08:00
13 changed files with 153 additions and 46 deletions
+3
View File
@@ -38,3 +38,6 @@ hs_err_*.log
replay_*.log replay_*.log
*.hprof *.hprof
*.jfr *.jfr
# kotlin
.kotlin
+2 -2
View File
@@ -20,8 +20,8 @@ Display hover text on the keybind modification button to show which mod provides
By default, to avoid disturbing users, information that is not useful to ordinary players is not shown. By default, to avoid disturbing users, information that is not useful to ordinary players is not shown.
If you need it, you can enable additional features in the config screen. If you need it, you can enable additional features in the config screen.
![](https://github.com/TheWhiteDog9487/WhoProvideThisKeybind/blob/%E4%B8%BB%E8%A6%81/%E5%9B%BE%E7%89%87/Snipaste_2026-09-03_20-38-14.png) ![](https://github.com/TheWhiteDog9487/WhoProvideThisKeybind/blob/%E4%B8%BB%E8%A6%81/%E5%9B%BE%E7%89%87/Snipaste_2026-09-03_20-38-14.png?raw=true)
![](https://github.com/TheWhiteDog9487/WhoProvideThisKeybind/blob/%E4%B8%BB%E8%A6%81/%E5%9B%BE%E7%89%87/Snipaste_2026-09-03_20-38-32.png) ![](https://github.com/TheWhiteDog9487/WhoProvideThisKeybind/blob/%E4%B8%BB%E8%A6%81/%E5%9B%BE%E7%89%87/Snipaste_2026-09-03_20-38-32.png?raw=true)
# Important Notes # Important Notes
**This mod cannot guarantee 100% accuracy!** **This mod cannot guarantee 100% accuracy!**
+10
View File
@@ -52,6 +52,16 @@ dependencies {
implementation("maven.modrinth:fuuu3xnx:bvFby61J") implementation("maven.modrinth:fuuu3xnx:bvFby61J")
// https://modrinth.com/mod/controlling/versions?l=fabric // https://modrinth.com/mod/controlling/versions?l=fabric
implementation("maven.modrinth:xv94TkTM:rAp2jljs") implementation("maven.modrinth:xv94TkTM:rAp2jljs")
// https://modrinth.com/mod/notenoughcrashes/versions?l=fabric
implementation("maven.modrinth:yM94ont6:3oHxOFPT")
// https://modrinth.com/mod/scoreboard-overhaul/versions
implementation("maven.modrinth:2YIQkF2v:HI1qkixZ")
// https://modrinth.com/plugin/plasmo-voice/versions?l=fabric
implementation("maven.modrinth:1bZhdhsH:I0T9OQcy")
// https://modrinth.com/mod/first-person-model/versions?l=fabric
implementation("maven.modrinth:H5XMjpHi:6sgz2HEq")
// https://modrinth.com/mod/entityculling/versions?l=fabric
implementation("maven.modrinth:NNAgCjsB:iiF6U3Ne")
} }
tasks.processResources { tasks.processResources {
+1 -1
View File
@@ -14,7 +14,7 @@ loom_version=1.17-SNAPSHOT
fabric_kotlin_version=1.13.13+kotlin.2.4.10 fabric_kotlin_version=1.13.13+kotlin.2.4.10
# Mod Properties # Mod Properties
version=1.0.0 version=1.2.0
group=xyz.thewhitedog9487 group=xyz.thewhitedog9487
OutputFileBasename=WhoProvideThisKeybind OutputFileBasename=WhoProvideThisKeybind
@@ -3,13 +3,9 @@ package xyz.thewhitedog9487.client.mixin;
import xyz.thewhitedog9487.client.MiscellaneousKt; import xyz.thewhitedog9487.client.MiscellaneousKt;
import xyz.thewhitedog9487.client.SettingsKt; import xyz.thewhitedog9487.client.SettingsKt;
import xyz.thewhitedog9487.client.WhoProvideThisKeybindModClient; import xyz.thewhitedog9487.client.WhoProvideThisKeybindModClient;
import xyz.thewhitedog9487.client.ModInfo;
import net.minecraft.client.KeyMapping; import net.minecraft.client.KeyMapping;
import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.options.controls.KeyBindsList; import net.minecraft.client.gui.screens.options.controls.KeyBindsList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@@ -0,0 +1,45 @@
package xyz.thewhitedog9487.client.mixin;
import com.mojang.blaze3d.platform.InputConstants;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.client.KeyMapping;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import xyz.thewhitedog9487.client.MiscellaneousKt;
import xyz.thewhitedog9487.client.ModInfo;
import java.util.Arrays;
import java.util.Optional;
@Mixin(KeyMapping.class)
public class KeyMappingMixin {
@Inject(method = "<init>(Ljava/lang/String;Lcom/mojang/blaze3d/platform/InputConstants$Type;ILnet/minecraft/client/KeyMapping$Category;I)V",
at = @At("TAIL"))
void DiscoverStackTrace(String name, InputConstants.Type type, int value, KeyMapping.Category category, int order, CallbackInfo ci) {
var StackTraceCLassNames = Arrays.stream(Thread.currentThread().getStackTrace())
.map(StackTraceElement::getClassName)
.toList();
for (String ClassName : StackTraceCLassNames) {
if (IsFrameworkClass(ClassName)) continue;
for (String PartOfClassName : ClassName.split("\\.")) {
Optional<ModContainer> CandidateModContainer = FabricLoader.getInstance().getModContainer(PartOfClassName);
if (CandidateModContainer.isPresent() && CandidateModContainer.get() != MiscellaneousKt.getVanillaMinecraftContainer()) {
var ModInfo = new ModInfo(CandidateModContainer.get());
MiscellaneousKt.getKeyMappingToMod().put((KeyMapping) (Object) this, ModInfo);
return; } } } }
@Unique
boolean IsFrameworkClass(@NotNull String ClassName) {
return ClassName.startsWith("java.")
|| ClassName.startsWith("jdk.")
|| ClassName.startsWith("sun.")
|| ClassName.startsWith("com.sun.")
|| ClassName.startsWith("kotlin.")
|| ClassName.startsWith("net.minecraft")
|| ClassName.startsWith("net.fabricmc")
|| ClassName.startsWith("org.spongepowered."); } }
@@ -7,29 +7,38 @@ import net.minecraft.client.KeyMapping
import net.minecraft.client.gui.components.Button import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.Tooltip import net.minecraft.client.gui.components.Tooltip
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import kotlin.io.path.pathString
val VanillaMinecraftContainer: ModContainer = FabricLoader val VanillaMinecraftContainer: ModContainer = FabricLoader
.getInstance() .getInstance()
.getModContainer("minecraft") .getModContainer("minecraft")
.get() .get()
val KeyMappingToMod: HashMap<KeyMapping, ModInfo> = HashMap()
val KeyMapping.Provider: ModInfo get() { val KeyMapping.Provider: ModInfo get() {
val KeyBindTranslationKey = name val KeyBindTranslationKey = name
val CandidateModIds = KeyBindTranslationKey.split(".") val CandidateModIds = KeyBindTranslationKey.split(".")
if (CandidateModIds.size == 1){
WhoProvideThisKeybindModClient.ClientLogger.warn("这是个什么东西,按键绑定ID只有一个部分,ID: $KeyBindTranslationKey")
return ModInfo(null) }
for (CandidateModId in CandidateModIds) { for (CandidateModId in CandidateModIds) {
val ModContainer = FabricLoader.getInstance().getModContainer(CandidateModId) val ModContainer = FabricLoader.getInstance().getModContainer(CandidateModId)
if (ModContainer.isPresent) { if (ModContainer.isPresent) {
return ModContainer.map(::ModInfo).get() } } return ModContainer.map(::ModInfo).get() } }
KeyMappingToMod[this]?.let { return it }
if (CandidateModIds.size == 1){
return ModInfo(null) }
return ModInfo(VanillaMinecraftContainer) } return ModInfo(VanillaMinecraftContainer) }
data class ModInfo(val ModContainerInstance: ModContainer?) { data class ModInfo(val ModContainerInstance: ModContainer?) {
val ID: String = ModContainerInstance?.metadata?.id ?: Component.translatable("special_provider_UNKNOWN").string val ID: String = ModContainerInstance?.metadata?.id ?: Component.translatable("special_string_UNKNOWN").string
val MetaData: ModMetadata? = ModContainerInstance?.metadata val MetaData: ModMetadata? = ModContainerInstance?.metadata
val DisplayName: String = MetaData?.name ?: Component.translatable("special_provider_UNKNOWN").string val DisplayName: String = MetaData?.name ?: Component.translatable("special_string_UNKNOWN").string
val HumanFriendlyName: Component = Component.translatableWithFallback("modmenu.nameTranslation." + (MetaData?.id ?: "unknown"), DisplayName) } val HumanFriendlyName: Component = Component.translatableWithFallback("modmenu.nameTranslation." + (MetaData?.id ?: "unknown"), DisplayName)
val FilePath: String get() {
val ModOrigin = ModContainerInstance?.origin
return try {
ModOrigin?.paths?.joinToString("\n") { it.pathString } ?: Component.translatable("special_string_UNKNOWN").string }
catch (_: UnsupportedOperationException) {
Component.translatable("special_string_UNKNOWN").string } } }
fun Button.SetNewTooltip(Key: KeyMapping) { fun Button.SetNewTooltip(Key: KeyMapping) {
if (SettingsInstance.ModEnabled == false) return if (SettingsInstance.ModEnabled == false) return
@@ -57,4 +66,9 @@ fun Button.SetNewTooltip(Key: KeyMapping) {
.append(Component.translatable("tooltip.key_id", .append(Component.translatable("tooltip.key_id",
Component.literal(Key.name))) } Component.literal(Key.name))) }
// 第四行:文件路径
if (SettingsInstance.ShowFilePath) {
NewTooltip.append(Component.literal("\n"))
.append(Component.translatable("tooltip.mod_file_path", Mod.FilePath)) }
setTooltip(Tooltip.create(NewTooltip)) } setTooltip(Tooltip.create(NewTooltip)) }
@@ -18,29 +18,44 @@ class ModMenuEntryPoint: ModMenuApi {
.name(Component.translatable("config.category.general")) .name(Component.translatable("config.category.general"))
.option { .option {
Option.createBuilder<Boolean>() Option.createBuilder<Boolean>()
.name(Component.translatable("option.ModEnabled")) .name(Component.translatable("option.general.ModEnabled"))
.description { OptionDescription.of(Component.translatable("option.ModEnabled.description") ) } .description { OptionDescription.of(Component.translatable("option.general.ModEnabled.description") ) }
.binding( true, { SettingsInstance.ModEnabled }, { SettingsInstance.ModEnabled = it } ) .binding( true, { SettingsInstance.ModEnabled }, { SettingsInstance.ModEnabled = it } )
.controller(BooleanControllerBuilder::create ) .controller(BooleanControllerBuilder::create )
.build() } .build() }
.build() }
.category {
ConfigCategory.createBuilder()
.name(Component.translatable("config.category.feature"))
.option { .option {
Option.createBuilder<Boolean>() Option.createBuilder<Boolean>()
.name(Component.translatable("option.ShowModID")) .name(Component.translatable("option.feature.ShowModID"))
.description { OptionDescription.of(Component.translatable("option.ShowModID.description") ) } .description { OptionDescription.of(Component.translatable("option.feature.ShowModID.description") ) }
.binding( false, { SettingsInstance.ShowModID }, { SettingsInstance.ShowModID = it } ) .binding( false, { SettingsInstance.ShowModID }, { SettingsInstance.ShowModID = it } )
.controller(BooleanControllerBuilder::create ) .controller(BooleanControllerBuilder::create )
.build() } .build() }
.option { .option {
Option.createBuilder<Boolean>() Option.createBuilder<Boolean>()
.name(Component.translatable("option.ShowKeybindID")) .name(Component.translatable("option.feature.ShowKeybindID"))
.description { OptionDescription.of(Component.translatable("option.ShowKeybindID.description") ) } .description { OptionDescription.of(Component.translatable("option.feature.ShowKeybindID.description") ) }
.binding( false, { SettingsInstance.ShowKeybindID }, { SettingsInstance.ShowKeybindID = it } ) .binding( false, { SettingsInstance.ShowKeybindID }, { SettingsInstance.ShowKeybindID = it } )
.controller(BooleanControllerBuilder::create ) .controller(BooleanControllerBuilder::create )
.build() } .build() }
.option { .option {
Option.createBuilder<Boolean>() Option.createBuilder<Boolean>()
.name(Component.translatable("option.ShowDebugInfo")) .name(Component.translatable("option.feature.ShowFilePath"))
.description { OptionDescription.of(Component.translatable("option.ShowDebugInfo.description") ) } .description { OptionDescription.of(Component.translatable("option.feature.ShowFilePath.description") ) }
.binding( false, { SettingsInstance.ShowFilePath }, { SettingsInstance.ShowFilePath = it } )
.controller(BooleanControllerBuilder::create )
.build() }
.build() }
.category {
ConfigCategory.createBuilder()
.name(Component.translatable("config.category.debug"))
.option {
Option.createBuilder<Boolean>()
.name(Component.translatable("option.debug.ShowDebugInfo"))
.description { OptionDescription.of(Component.translatable("option.debug.ShowDebugInfo.description") ) }
.binding( false, { SettingsInstance.ShowDebugInfo }, { SettingsInstance.ShowDebugInfo = it } ) .binding( false, { SettingsInstance.ShowDebugInfo }, { SettingsInstance.ShowDebugInfo = it } )
.controller(BooleanControllerBuilder::create ) .controller(BooleanControllerBuilder::create )
.build() } .build() }
@@ -7,9 +7,12 @@ import net.fabricmc.loader.api.FabricLoader
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds import java.nio.file.StandardWatchEventKinds
import java.nio.file.StandardWatchEventKinds.*
const val CurrentConfigurationVersionCode: Long = 0 const val CurrentConfigurationVersionCode: Long = 0
/**
* @see ModMenuEntryPoint\
*/
class Settings { class Settings {
@SerialEntry(comment = @SerialEntry(comment =
""" """
@@ -57,11 +60,20 @@ class Settings {
取值范围:true false 取值范围:true false
默认值:false 默认值:false
""") """)
var ShowDebugInfo = false } var ShowDebugInfo = false
@SerialEntry(comment =
"""
是否在悬停信息中显示Mod的文件路径
类型:布尔值
取值范围:true false
默认值:false
""")
var ShowFilePath = false }
var SettingsHandler: ConfigClassHandler<Settings>? = null var SettingsHandler: ConfigClassHandler<Settings>? = null
val ConfigFilePath = FabricLoader val ConfigFilePath: Path = FabricLoader
.getInstance() .getInstance()
.configDir .configDir
.resolve(WhoProvideThisKeybindModClient.FriendlyModID + ".json5") .resolve(WhoProvideThisKeybindModClient.FriendlyModID + ".json5")
@@ -77,7 +89,7 @@ fun RegisterConfigFileReloadWatcher() {
val ConfigDirectory = ConfigFilePath.parent val ConfigDirectory = ConfigFilePath.parent
try { try {
val WatchService = FileSystems.getDefault().newWatchService() val WatchService = FileSystems.getDefault().newWatchService()
ConfigDirectory.register(WatchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE) ConfigDirectory.register(WatchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE)
while (!Thread.currentThread().isInterrupted) { while (!Thread.currentThread().isInterrupted) {
val WatchKey = WatchService.take() val WatchKey = WatchService.take()
var ShouldReload = false var ShouldReload = false
@@ -95,7 +107,7 @@ fun RegisterConfigFileReloadWatcher() {
WhoProvideThisKeybindModClient.ClientLogger.info("检测到配置文件变更,已重新加载设置。") } WhoProvideThisKeybindModClient.ClientLogger.info("检测到配置文件变更,已重新加载设置。") }
if (!WatchKey.reset()) { if (!WatchKey.reset()) {
break } } break } }
} catch (e: InterruptedException) { } catch (_: InterruptedException) {
Thread.currentThread().interrupt() Thread.currentThread().interrupt()
} catch (e: IOException) { } catch (e: IOException) {
throw RuntimeException(e) } } } throw RuntimeException(e) } } }
@@ -1,13 +1,14 @@
package xyz.thewhitedog9487.client package xyz.thewhitedog9487.client
import net.fabricmc.api.ClientModInitializer import net.fabricmc.api.ClientModInitializer
import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import xyz.thewhitedog9487.client.Event.RegisterClientLifecycleEvents import xyz.thewhitedog9487.client.Event.RegisterClientLifecycleEvents
object WhoProvideThisKeybindModClient : ClientModInitializer { object WhoProvideThisKeybindModClient : ClientModInitializer {
const val ModID: String = "whoprovidethiskeybind" const val ModID: String = "whoprovidethiskeybind"
const val FriendlyModID = "WhoProvideThisKeybind" const val FriendlyModID = "WhoProvideThisKeybind"
val ClientLogger = LoggerFactory.getLogger(FriendlyModID) val ClientLogger: Logger = LoggerFactory.getLogger(FriendlyModID)
override fun onInitializeClient() { override fun onInitializeClient() {
RegisterClientLifecycleEvents() } } RegisterClientLifecycleEvents() } }
@@ -3,7 +3,8 @@
"package": "xyz.thewhitedog9487.client.mixin", "package": "xyz.thewhitedog9487.client.mixin",
"compatibilityLevel": "JAVA_25", "compatibilityLevel": "JAVA_25",
"client": [ "client": [
"KeyBindsListKeyEntryMixin" "KeyBindsListKeyEntryMixin",
"KeyMappingMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
@@ -5,16 +5,21 @@
"link.blog": "TheWhiteDog9487's Blog", "link.blog": "TheWhiteDog9487's Blog",
"title.config": "Who Provides This Keybind?", "title.config": "Who Provides This Keybind?",
"config.category.general": "General", "config.category.general": "General",
"option.ModEnabled": "Enable Mod Functionality", "config.category.feature": "Feature",
"option.ModEnabled.description": "Changes take effect immediately after modification and save without restarting the game", "config.category.debug": "Debug",
"option.ShowModID": "Show Mod ID", "option.general.ModEnabled": "Enable Mod Functionality",
"option.ShowModID.description": "Display the Mod ID of the provider in the tooltip", "option.general.ModEnabled.description": "Changes take effect immediately after modification and save without restarting the game",
"option.ShowKeybindID": "Show Keybind ID", "option.feature.ShowModID": "Show Mod ID",
"option.ShowKeybindID.description": "Display the keybind's own ID in the tooltip", "option.feature.ShowModID.description": "Display the Mod ID of the provider in the tooltip",
"option.ShowDebugInfo": "Show Debug Info", "option.feature.ShowKeybindID": "Show Keybind ID",
"option.ShowDebugInfo.description": "This information is used to troubleshoot issues related to this mod; it generally does not need to be enabled", "option.feature.ShowKeybindID.description": "Display the keybind's own ID in the tooltip",
"option.feature.ShowFilePath": "Show File Path",
"option.feature.ShowFilePath.description": "Display the mod file path in the information",
"option.debug.ShowDebugInfo": "Show Debug Info",
"option.debug.ShowDebugInfo.description": "This information is used to troubleshoot issues related to this mod; it generally does not need to be enabled",
"tooltip.provided_by": "Provided by: %s", "tooltip.provided_by": "Provided by: %s",
"tooltip.provider_mod_id": "Provider Mod ID: %s", "tooltip.provider_mod_id": "Provider Mod ID: %s",
"tooltip.key_id": "Keybind ID: %s", "tooltip.key_id": "Keybind ID: %s",
"special_provider_UNKNOWN": "Unknown" "tooltip.mod_file_path": "Mod File Path: %s",
"special_string_UNKNOWN": "Unknown"
} }
@@ -5,16 +5,21 @@
"link.blog": "TheWhiteDog9487的博客", "link.blog": "TheWhiteDog9487的博客",
"title.config": "谁在提供这个键位?", "title.config": "谁在提供这个键位?",
"config.category.general": "通用", "config.category.general": "通用",
"option.ModEnabled": "是否启用模组功能", "config.category.feature": "功能",
"option.ModEnabled.description": "无需重启游戏,修改并保存后立刻生效", "config.category.debug": "调试",
"option.ShowModID": "是否显示Mod ID", "option.general.ModEnabled": "是否启用模组功能",
"option.ShowModID.description": "在提示信息中显示提供该按键的Mod的ID", "option.general.ModEnabled.description": "无需重启游戏,修改并保存后立刻生效",
"option.ShowKeybindID": "是否显示键位ID", "option.feature.ShowModID": "是否显示Mod ID",
"option.ShowKeybindID.description": "在提示信息中显示该按键本身的ID", "option.feature.ShowModID.description": "在提示信息中显示提供该按键的Mod的ID",
"option.ShowDebugInfo": "是否显示调试信息", "option.feature.ShowKeybindID": "是否显示键位ID",
"option.ShowDebugInfo.description": "这些信息用于出现和本Mod相关的问题时进行排查,一般情况下不需要开启", "option.feature.ShowKeybindID.description": "在提示信息中显示该按键本身的ID",
"option.feature.ShowFilePath": "是否显示文件路径",
"option.feature.ShowFilePath.description": "在信息中显示Mod的文件路径",
"option.debug.ShowDebugInfo": "是否显示调试信息",
"option.debug.ShowDebugInfo.description": "这些信息用于出现和本Mod相关的问题时进行排查,一般情况下不需要开启",
"tooltip.provided_by": "提供者:%s", "tooltip.provided_by": "提供者:%s",
"tooltip.provider_mod_id": "提供者ID%s", "tooltip.provider_mod_id": "提供者ID%s",
"tooltip.key_id": "键位ID%s", "tooltip.key_id": "键位ID%s",
"special_provider_UNKNOWN": "未知" "tooltip.mod_file_path": "Mod文件路径:%s",
"special_string_UNKNOWN": "未知"
} }