抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

简而言之,就是检查.jar文件内的META-INF/MANIFEST.MF文件,其中的Main-Class字段具有每种核心的特征。

引言

在开发开服器时,区分服务端类型是很重要的,因为单凭文件扩展名无法判断用户提供的.jar文件究竟是什么,例如反人类的Forge核心,永远不给下载核心本体,只让下载安装器。除此之外,如果客户端核心滥竽充数,也会导致某些奇怪的问题。

然而,有一个办法可以大致上区分各种服务器核心,那就是检查.jar文件内的META-INF/MANIFEST.MF文件。

关于MAINFEST.MF

MANIFEST.MF文件是Java平台的一种规范,用于定义和管理Java应用程序的组件、库和模块。它是JAR文件中的一个纯文本文件,遵循特定的格式规范。

在JAR文件中,MANIFEST.MF文件必须位于META-INF目录下,且一个JAR文件中只能有一个MANIFEST.MF文件。

在MANIFEST.MF文件中,Main-Class字段向Java虚拟机指明了该文件的主类,以使jar文件能够正常执行。在较新的MC版本中都会具有Main-Class字段(目前已知的只有远古版本没有)。由于不同的核心都会有自己的主类名称,因此可以通过这个特征在MC开服器中分辨其核心类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
public class CoreValidationService
{
public enum CoreType// 核心类型的枚举
{
Error,
Unknown,
Client,
ForgeInstaller,
FabricInstaller,
Forge,
Fabric,
Arclight,
CatServer,
CraftBukkit,
Leaves,
LightFall,
Mohist,
Paper,
Vanilla,
Velocity,
}
public static CoreType Validate(string? filePath, out string ErrorMessage)// 校验核心类型
{
ErrorMessage = "";
if (string.IsNullOrEmpty(filePath))
{
ErrorMessage = "选定的路径为空";
return CoreType.Error;
}
if (!File.Exists(filePath))
{
ErrorMessage = "选定的文件/路径不存在";
return CoreType.Error;
}
string? JarMainClass = GetMainClass(filePath);
if (JarMainClass == null) return CoreType.Unknown;
else if (JarMainClass.StartsWith("Access denied") || JarMainClass.StartsWith("Error"))
{
ErrorMessage = JarMainClass;
return CoreType.Error;
}
else
{
return JarMainClass switch
{
"net.minecraft.server.MinecraftServer" => CoreType.Vanilla,
"net.minecraft.bundler.Main" => CoreType.Vanilla,
"net.minecraft.client.Main" => CoreType.Client,
"net.minecraftforge.installer.SimpleInstaller" => CoreType.ForgeInstaller,
"net.fabricmc.installer.Main" => CoreType.FabricInstaller,
"net.fabricmc.installer.ServerLauncher" => CoreType.Fabric,
"io.izzel.arclight.server.Launcher" => CoreType.Arclight,
"catserver.server.CatServerLaunch" => CoreType.CatServer,
"foxlaunch.FoxServerLauncher" => CoreType.CatServer,
"org.bukkit.craftbukkit.Main" => CoreType.CraftBukkit,
"org.bukkit.craftbukkit.bootstrap.Main" => CoreType.CraftBukkit,
"io.papermc.paperclip.Main" => CoreType.Paper,
"org.leavesmc.leavesclip.Main" => CoreType.Leaves,
"net.md_5.bungee.Bootstrap" => CoreType.LightFall,
"com.mohistmc.MohistMCStart" => CoreType.Mohist,
"com.mohistmc.MohistMC" => CoreType.Mohist,
"com.destroystokyo.paperclip.Paperclip" => CoreType.Paper,
"com.velocitypowered.proxy.Velocity" => CoreType.Velocity,
_ => CoreType.Unknown,
};
}
}
// the following code is taken from https://github.com/Orange-Icepop/JavaMainClassFinder
public static string? GetMainClass(string jarFilePath)
{
try
{
using FileStream stream = new FileStream(jarFilePath, FileMode.Open);
using ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read);
ZipArchiveEntry? manifestEntry = archive.Entries.FirstOrDefault(entry => entry.FullName == "META-INF/MANIFEST.MF");
if (manifestEntry != null)// 对于较新版本的MC,MANIFEST.MF中应当包含Main-Class字段
{
using StreamReader reader = new StreamReader(manifestEntry.Open());
string manifestContent = reader.ReadToEnd();
return FindMainClassLine(manifestContent);
}
return null;
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Access denied: " + ex.Message);
return "Access denied: " + ex.Message;
}
catch (IOException ex)
{
Console.WriteLine("IO error: " + ex.Message);
return "Error reading file: " + ex.Message;
}
catch (Exception ex)
{
Console.WriteLine("Error reading jar file: " + ex.Message);
return "Error reading jar file: " + ex.Message;
}
}

public static string? FindMainClassLine(string manifestContent)
{
string[] lines = manifestContent.Split(["\r\n", "\r", "\n"], StringSplitOptions.None);
foreach (string line in lines)
{
if (line.StartsWith("Main-Class:"))
{
return line.Substring("Main-Class:".Length).Trim();
}
}
return null;
}
}

最重要的信息是以下这段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
return JarMainClass switch//使用switch表达式返回值
{
"net.minecraft.server.MinecraftServer" => CoreType.Vanilla,
"net.minecraft.bundler.Main" => CoreType.Vanilla,
"net.minecraft.client.Main" => CoreType.Client,
"net.minecraftforge.installer.SimpleInstaller" => CoreType.ForgeInstaller,
"net.fabricmc.installer.Main" => CoreType.FabricInstaller,
"net.fabricmc.installer.ServerLauncher" => CoreType.Fabric,
"io.izzel.arclight.server.Launcher" => CoreType.Arclight,
"catserver.server.CatServerLaunch" => CoreType.CatServer,
"foxlaunch.FoxServerLauncher" => CoreType.CatServer,
"org.bukkit.craftbukkit.Main" => CoreType.CraftBukkit,
"org.bukkit.craftbukkit.bootstrap.Main" => CoreType.CraftBukkit,
"io.papermc.paperclip.Main" => CoreType.Paper,
"org.leavesmc.leavesclip.Main" => CoreType.Leaves,
"net.md_5.bungee.Bootstrap" => CoreType.LightFall,
"com.mohistmc.MohistMCStart" => CoreType.Mohist,
"com.mohistmc.MohistMC" => CoreType.Mohist,
"com.destroystokyo.paperclip.Paperclip" => CoreType.Paper,
"com.velocitypowered.proxy.Velocity" => CoreType.Velocity,
_ => CoreType.Unknown,
};

比较需要注意的是,某些核心在不同的版本中会具有不同的主类名称,例如原版核心的最近几个版本(实测最晚为1.21)的主类名称从net.minecraft.server.MinecraftServer换为了net.minecraft.bundler.Main,但是客户端的主类没改。

还有一部分核心由于底层相同,并没有修改主类名称,例如Folia和Paper的主类名称就相同。

部分核心的问题

之前的代码要特意标注来自LSL v0.08是有原因的。在0.08之后,我对所有类型的核心进行了测试,发现Mohist核心与Folia核心会在解析时抛出一大堆的ArgumentOutOfRangeException异常。经过查询,这是在解析时间戳时出现的问题,非常有可能是因为这些核心的时间戳异常导致的。当然,这不影响核心文件的执行,但是对于上面我们依赖的System.IO.Compression命名空间中的方法具有致命的执行效率打击,也就是在0.08版本和0.07.1版本中添加某些核心时会导致整个应用程序卡死的原因。

为了解决这个问题,我在0.08.1版本中引入了SharpZipLib库,这个库不会理会时间戳异常,因此完美规避掉了这个问题。完整实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using ICSharpCode.SharpZipLib.Zip;
using System;
using System.IO;

namespace LSL.Services.Validators
{
public class CoreValidationService
{
public enum CoreType
{
Error,
Unknown,
Client,
ForgeInstaller,
FabricInstaller,
Forge,
Fabric,
Arclight,
CatServer,
CraftBukkit,
Leaves,
LightFall,
Mohist,
Paper,
Vanilla,
Velocity,
}
public static CoreType Validate(string? filePath, out string ErrorMessage)// 校验核心类型
{
ErrorMessage = "";
if (string.IsNullOrEmpty(filePath))
{
ErrorMessage = "选定的路径为空";
return CoreType.Error;

}
if (!File.Exists(filePath))
{
ErrorMessage = "选定的文件/路径不存在";
return CoreType.Error;
}
string? JarMainClass = GetMainClass(filePath);
if (JarMainClass == null) return CoreType.Unknown;
else if (JarMainClass.StartsWith("Access denied") || JarMainClass.StartsWith("Error"))
{
ErrorMessage = JarMainClass;
return CoreType.Error;
}
else
{
return JarMainClass switch
{
"net.minecraft.server.MinecraftServer" => CoreType.Vanilla,
"net.minecraft.bundler.Main" => CoreType.Vanilla,
"net.minecraft.client.Main" => CoreType.Client,
"net.minecraftforge.installer.SimpleInstaller" => CoreType.ForgeInstaller,
"net.fabricmc.installer.Main" => CoreType.FabricInstaller,
"net.fabricmc.installer.ServerLauncher" => CoreType.Fabric,
"io.izzel.arclight.server.Launcher" => CoreType.Arclight,
"catserver.server.CatServerLaunch" => CoreType.CatServer,
"foxlaunch.FoxServerLauncher" => CoreType.CatServer,
"org.bukkit.craftbukkit.Main" => CoreType.CraftBukkit,
"org.bukkit.craftbukkit.bootstrap.Main" => CoreType.CraftBukkit,
"io.papermc.paperclip.Main" => CoreType.Paper,
"org.leavesmc.leavesclip.Main" => CoreType.Leaves,
"net.md_5.bungee.Bootstrap" => CoreType.LightFall,
"com.mohistmc.MohistMCStart" => CoreType.Mohist,
"com.mohistmc.MohistMC" => CoreType.Mohist,
"com.destroystokyo.paperclip.Paperclip" => CoreType.Paper,
"com.velocitypowered.proxy.Velocity" => CoreType.Velocity,
_ => CoreType.Unknown,
};
}
}
// the following code is taken from https://github.com/Orange-Icepop/JavaMainClassFinder
public static string? GetMainClass(string jarFilePath)
{
try
{
using FileStream stream = new FileStream(jarFilePath, FileMode.Open);
using (ZipFile zipFile = new ZipFile(stream))
{
foreach (ZipEntry entry in zipFile)
{
if (entry.IsDirectory)
continue;

if (entry.Name == "META-INF/MANIFEST.MF")// 对于较新版本的MC,MANIFEST.MF中应当包含Main-Class字段
{
using (var fstream = zipFile.GetInputStream(entry))
using (StreamReader reader = new StreamReader(fstream))
{
string manifestContent = reader.ReadToEnd();
return FindMainClassLine(manifestContent);
}
}
}
}
return null;
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Access denied: " + ex.Message);
return "Access denied: " + ex.Message;
}
catch (IOException ex)
{
Console.WriteLine("IO error: " + ex.Message);
return "Error reading file: " + ex.Message;
}
catch (Exception ex)
{
Console.WriteLine("Error reading jar file: " + ex.Message);
return "Error reading jar file: " + ex.Message;
}
}

public static string? FindMainClassLine(string manifestContent)
{
string[] lines = manifestContent.Split(["\r\n", "\r", "\n"], StringSplitOptions.None);
foreach (string line in lines)
{
if (line.StartsWith("Main-Class:"))
{
return line.Substring("Main-Class:".Length).Trim();
}
}
return null;
}
}
}

JavaMainClassFinder也同步进行了更新。问题解决!

评论