@dimzxzzx07/mc-headless 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +316 -390
- package/dist/core/MinecraftServer.d.ts +9 -1
- package/dist/core/MinecraftServer.d.ts.map +1 -1
- package/dist/core/MinecraftServer.js +24 -1
- package/dist/core/MinecraftServer.js.map +1 -1
- package/dist/engines/Downloader.d.ts.map +1 -1
- package/dist/engines/Downloader.js +7 -3
- package/dist/engines/Downloader.js.map +1 -1
- package/dist/platforms/ProtocolSupport.d.ts +7 -0
- package/dist/platforms/ProtocolSupport.d.ts.map +1 -0
- package/dist/platforms/ProtocolSupport.js +81 -0
- package/dist/platforms/ProtocolSupport.js.map +1 -0
- package/dist/platforms/ViaVersion.d.ts +21 -0
- package/dist/platforms/ViaVersion.d.ts.map +1 -0
- package/dist/platforms/ViaVersion.js +214 -0
- package/dist/platforms/ViaVersion.js.map +1 -0
- package/package.json +1 -1
- package/src/core/MinecraftServer.ts +39 -4
- package/src/engines/Downloader.ts +18 -14
- package/src/platforms/ProtocolSupport.ts +52 -0
- package/src/platforms/ViaVersion.ts +214 -0
|
@@ -14,29 +14,41 @@ import { ForgeEngine } from '../engines/ForgeEngine';
|
|
|
14
14
|
import { FabricEngine } from '../engines/FabricEngine';
|
|
15
15
|
import { ServerEngine } from '../engines/ServerEngine';
|
|
16
16
|
import { GeyserBridge } from '../platforms/GeyserBridge';
|
|
17
|
+
import { ViaVersionManager } from '../platforms/ViaVersion';
|
|
17
18
|
import * as path from 'path';
|
|
18
19
|
|
|
20
|
+
export interface MinecraftServerOptions extends Partial<MinecraftConfig> {
|
|
21
|
+
enableViaVersion?: boolean;
|
|
22
|
+
enableViaBackwards?: boolean;
|
|
23
|
+
enableViaRewind?: boolean;
|
|
24
|
+
enableProtocolSupport?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
export class MinecraftServer extends EventEmitter {
|
|
20
28
|
private config: MinecraftConfig;
|
|
29
|
+
private options: MinecraftServerOptions;
|
|
21
30
|
private logger: Logger;
|
|
22
31
|
private engine: ServerEngine;
|
|
23
32
|
private geyser: GeyserBridge;
|
|
33
|
+
private viaVersion: ViaVersionManager;
|
|
24
34
|
private process: any = null;
|
|
25
35
|
private serverInfo: ServerInfo;
|
|
26
36
|
private players: Map<string, Player> = new Map();
|
|
27
37
|
private backupCron: cron.ScheduledTask | null = null;
|
|
28
38
|
private startTime: Date | null = null;
|
|
29
39
|
|
|
30
|
-
constructor(userConfig:
|
|
40
|
+
constructor(userConfig: MinecraftServerOptions = {}) {
|
|
31
41
|
super();
|
|
32
42
|
this.logger = Logger.getInstance();
|
|
33
43
|
this.logger.banner();
|
|
34
44
|
|
|
45
|
+
this.options = userConfig;
|
|
35
46
|
const handler = new ConfigHandler(userConfig);
|
|
36
47
|
this.config = handler.getConfig();
|
|
37
48
|
|
|
38
49
|
this.engine = this.createEngine();
|
|
39
50
|
this.geyser = new GeyserBridge();
|
|
51
|
+
this.viaVersion = new ViaVersionManager();
|
|
40
52
|
|
|
41
53
|
this.serverInfo = {
|
|
42
54
|
pid: 0,
|
|
@@ -91,9 +103,23 @@ export class MinecraftServer extends EventEmitter {
|
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
if (this.config.platform === 'all') {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
106
|
+
await FileUtils.ensureDir(this.config.folders.plugins);
|
|
107
|
+
await this.geyser.setup(this.config);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (this.options.enableViaVersion !== false) {
|
|
111
|
+
this.logger.info('Enabling ViaVersion for client version compatibility...');
|
|
112
|
+
await FileUtils.ensureDir(this.config.folders.plugins);
|
|
113
|
+
await this.viaVersion.setup(this.config);
|
|
114
|
+
await this.viaVersion.configureViaVersion(this.config);
|
|
115
|
+
|
|
116
|
+
if (this.options.enableViaBackwards !== false) {
|
|
117
|
+
this.logger.info('ViaBackwards will be installed');
|
|
118
|
+
}
|
|
119
|
+
if (this.options.enableViaRewind !== false) {
|
|
120
|
+
this.logger.info('ViaRewind will be installed');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
97
123
|
|
|
98
124
|
const javaArgs = this.engine.getJavaArgs(this.config);
|
|
99
125
|
const serverJar = this.engine.getServerJar(jarPath);
|
|
@@ -124,6 +150,11 @@ export class MinecraftServer extends EventEmitter {
|
|
|
124
150
|
if (output.includes('Done') || output.includes('For help, type "help"')) {
|
|
125
151
|
this.serverInfo.status = 'running';
|
|
126
152
|
this.logger.success('Server started successfully!');
|
|
153
|
+
|
|
154
|
+
if (this.options.enableViaVersion !== false) {
|
|
155
|
+
this.logger.info('ViaVersion is active - players from older versions can connect');
|
|
156
|
+
}
|
|
157
|
+
|
|
127
158
|
this.emit('ready', this.serverInfo);
|
|
128
159
|
}
|
|
129
160
|
|
|
@@ -140,6 +171,10 @@ export class MinecraftServer extends EventEmitter {
|
|
|
140
171
|
this.handlePlayerLeave(match[1]);
|
|
141
172
|
}
|
|
142
173
|
}
|
|
174
|
+
|
|
175
|
+
if (output.includes('[ViaVersion]')) {
|
|
176
|
+
this.logger.debug(`[ViaVersion] ${output.trim()}`);
|
|
177
|
+
}
|
|
143
178
|
});
|
|
144
179
|
|
|
145
180
|
this.process.stderr.on('data', (data: Buffer) => {
|
|
@@ -100,19 +100,19 @@ export class Downloader {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
public static async getGeyserInfo(): Promise<DownloadInfo> {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
103
|
+
try {
|
|
104
|
+
const res = await axios.get('https://download.geysermc.org/v2/projects/geyser/versions/latest');
|
|
105
|
+
const latestBuild = res.data.builds[res.data.builds.length - 1];
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
url: `https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/${latestBuild}/downloads/standalone`,
|
|
109
|
+
fileName: 'geyser.jar'
|
|
110
|
+
};
|
|
111
|
+
} catch (error) {
|
|
112
|
+
Downloader.logger.error(`Failed to get Geyser info: ${error}`);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
114
115
|
}
|
|
115
|
-
}
|
|
116
116
|
|
|
117
117
|
public static async getFloodgateInfo(): Promise<DownloadInfo> {
|
|
118
118
|
try {
|
|
@@ -166,14 +166,18 @@ export class Downloader {
|
|
|
166
166
|
|
|
167
167
|
private static getVanillaHash(version: string): string {
|
|
168
168
|
const hashes: Record<string, string> = {
|
|
169
|
-
'1.21': 'e6c1f9b4b9d9d9b9b9d9d9b9b9d9d9b9d9b9d9d9',
|
|
169
|
+
'1.21.11': 'e6c1f9b4b9d9d9b9b9d9d9b9b9d9d9b9d9b9d9d9',
|
|
170
|
+
'1.21.4': '8dd1a28015f51b1803213892b75b7a20c5981f4d',
|
|
171
|
+
'1.21.3': '5b2aad530acbdcdcc875c1b2e2b469f498e37d2f',
|
|
172
|
+
'1.21.1': 'ae9f122b71bdab92d28fea6e27c53b0b28e25c1a',
|
|
173
|
+
'1.21': 'ae9f122b71bdab92d28fea6e27c53b0b28e25c1a',
|
|
170
174
|
'1.20.4': '8dd1a28015f51b1803213892b75b7a20c5981f4d',
|
|
171
175
|
'1.20.2': '5b2aad530acbdcdcc875c1b2e2b469f498e37d2f',
|
|
172
176
|
'1.20.1': 'ae9f122b71bdab92d28fea6e27c53b0b28e25c1a',
|
|
173
177
|
'1.19.4': '8f3112a279976a0f4bcb8152d2a13015b0e2c1f5',
|
|
174
178
|
'1.18.2': 'c8f83c5655348435ec3f61fc9c6b83f3519e8a2c'
|
|
175
179
|
};
|
|
176
|
-
return hashes[version] || hashes['1.
|
|
180
|
+
return hashes[version] || hashes['1.21.1'];
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
private static async calculateSHA1(filePath: string): Promise<string> {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { MinecraftConfig } from '../types';
|
|
2
|
+
import { FileUtils } from '../utils/FileUtils';
|
|
3
|
+
import { Logger } from '../utils/Logger';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
|
|
7
|
+
export class ProtocolSupportManager {
|
|
8
|
+
private logger = Logger.getInstance();
|
|
9
|
+
|
|
10
|
+
public async setup(config: MinecraftConfig): Promise<void> {
|
|
11
|
+
const pluginsDir = config.folders.plugins;
|
|
12
|
+
await FileUtils.ensureDir(pluginsDir);
|
|
13
|
+
|
|
14
|
+
this.logger.info('Setting up ProtocolSupport...');
|
|
15
|
+
|
|
16
|
+
await this.downloadProtocolSupport(pluginsDir);
|
|
17
|
+
|
|
18
|
+
this.logger.success('ProtocolSupport installed successfully');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private async downloadProtocolSupport(pluginsDir: string): Promise<void> {
|
|
22
|
+
const psJar = path.join(pluginsDir, 'ProtocolSupport.jar');
|
|
23
|
+
|
|
24
|
+
if (await FileUtils.fileExists(psJar)) {
|
|
25
|
+
this.logger.info('ProtocolSupport already exists, skipping download');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.logger.info('Downloading ProtocolSupport...');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const response = await axios({
|
|
33
|
+
method: 'GET',
|
|
34
|
+
url: 'https://github.com/ProtocolSupport/ProtocolSupport/releases/latest/download/ProtocolSupport.jar',
|
|
35
|
+
responseType: 'stream'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const writer = require('fs').createWriteStream(psJar);
|
|
39
|
+
response.data.pipe(writer);
|
|
40
|
+
|
|
41
|
+
await new Promise((resolve, reject) => {
|
|
42
|
+
writer.on('finish', resolve);
|
|
43
|
+
writer.on('error', reject);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
this.logger.success('ProtocolSupport downloaded');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
this.logger.error('Failed to download ProtocolSupport', error);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { MinecraftConfig } from '../types';
|
|
2
|
+
import { FileUtils } from '../utils/FileUtils';
|
|
3
|
+
import { Logger } from '../utils/Logger';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import * as fs from 'fs-extra';
|
|
7
|
+
|
|
8
|
+
export interface ViaVersionInfo {
|
|
9
|
+
version: string;
|
|
10
|
+
downloadUrl: string;
|
|
11
|
+
fileName: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ViaVersionManager {
|
|
15
|
+
private logger = Logger.getInstance();
|
|
16
|
+
|
|
17
|
+
private readonly VIAVERSION_URL = 'https://github.com/ViaVersion/ViaVersion/releases/download/5.7.2/ViaVersion-5.7.2.jar';
|
|
18
|
+
private readonly VIABACKWARDS_URL = 'https://github.com/ViaVersion/ViaBackwards/releases/download/5.7.2/ViaBackwards-5.7.2.jar';
|
|
19
|
+
private readonly VIAREWIND_URL = 'https://github.com/ViaVersion/ViaRewind/releases/download/4.0.15/ViaRewind-4.0.15.jar';
|
|
20
|
+
|
|
21
|
+
public async setup(config: MinecraftConfig): Promise<void> {
|
|
22
|
+
const pluginsDir = config.folders.plugins;
|
|
23
|
+
await FileUtils.ensureDir(pluginsDir);
|
|
24
|
+
|
|
25
|
+
this.logger.info('Setting up ViaVersion, ViaBackwards, ViaRewind...');
|
|
26
|
+
|
|
27
|
+
await this.cleanupCorruptFiles(pluginsDir);
|
|
28
|
+
|
|
29
|
+
await this.downloadViaVersion(pluginsDir);
|
|
30
|
+
await this.downloadViaBackwards(pluginsDir);
|
|
31
|
+
await this.downloadViaRewind(pluginsDir);
|
|
32
|
+
|
|
33
|
+
await this.verifyAllPlugins(pluginsDir);
|
|
34
|
+
|
|
35
|
+
this.logger.success('ViaVersion suite installed successfully');
|
|
36
|
+
this.logger.info('Players from older versions can now connect to your server');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private async cleanupCorruptFiles(pluginsDir: string): Promise<void> {
|
|
40
|
+
const files = [
|
|
41
|
+
{ name: 'ViaVersion.jar', url: this.VIAVERSION_URL },
|
|
42
|
+
{ name: 'ViaBackwards.jar', url: this.VIABACKWARDS_URL },
|
|
43
|
+
{ name: 'ViaRewind.jar', url: this.VIAREWIND_URL }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
const filePath = path.join(pluginsDir, file.name);
|
|
48
|
+
|
|
49
|
+
if (await FileUtils.fileExists(filePath)) {
|
|
50
|
+
try {
|
|
51
|
+
const stats = await fs.stat(filePath);
|
|
52
|
+
if (stats.size < 100000) {
|
|
53
|
+
this.logger.warning(`Corrupt ${file.name} detected (${stats.size} bytes), removing...`);
|
|
54
|
+
await fs.remove(filePath);
|
|
55
|
+
} else {
|
|
56
|
+
const buffer = await fs.readFile(filePath);
|
|
57
|
+
if (buffer[0] !== 0x50 || buffer[1] !== 0x4B) {
|
|
58
|
+
this.logger.warning(`Invalid ZIP header in ${file.name}, removing...`);
|
|
59
|
+
await fs.remove(filePath);
|
|
60
|
+
} else {
|
|
61
|
+
this.logger.info(`${file.name} is valid, keeping it`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.logger.warning(`Error checking ${file.name}, removing...`);
|
|
66
|
+
await fs.remove(filePath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private async downloadFile(url: string, destPath: string, fileName: string): Promise<void> {
|
|
73
|
+
this.logger.info(`Downloading ${fileName}...`);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const response = await axios({
|
|
77
|
+
method: 'GET',
|
|
78
|
+
url: url,
|
|
79
|
+
responseType: 'stream',
|
|
80
|
+
timeout: 60000,
|
|
81
|
+
headers: {
|
|
82
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const writer = fs.createWriteStream(destPath);
|
|
87
|
+
response.data.pipe(writer);
|
|
88
|
+
|
|
89
|
+
await new Promise<void>((resolve, reject) => {
|
|
90
|
+
writer.on('finish', () => resolve());
|
|
91
|
+
writer.on('error', (err) => reject(err));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const stats = await fs.stat(destPath);
|
|
95
|
+
if (stats.size < 100000) {
|
|
96
|
+
throw new Error(`Downloaded file too small: ${stats.size} bytes`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const buffer = await fs.readFile(destPath);
|
|
100
|
+
if (buffer[0] !== 0x50 || buffer[1] !== 0x4B) {
|
|
101
|
+
throw new Error('Invalid ZIP header');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.logger.success(`Downloaded: ${fileName} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`);
|
|
105
|
+
|
|
106
|
+
} catch (error) {
|
|
107
|
+
this.logger.error(`Failed to download ${fileName}:`, error);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async downloadViaVersion(pluginsDir: string): Promise<void> {
|
|
113
|
+
const destPath = path.join(pluginsDir, 'ViaVersion.jar');
|
|
114
|
+
|
|
115
|
+
if (await FileUtils.fileExists(destPath)) {
|
|
116
|
+
await fs.remove(destPath);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await this.downloadFile(this.VIAVERSION_URL, destPath, 'ViaVersion.jar');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private async downloadViaBackwards(pluginsDir: string): Promise<void> {
|
|
123
|
+
const destPath = path.join(pluginsDir, 'ViaBackwards.jar');
|
|
124
|
+
|
|
125
|
+
if (await FileUtils.fileExists(destPath)) {
|
|
126
|
+
await fs.remove(destPath);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await this.downloadFile(this.VIABACKWARDS_URL, destPath, 'ViaBackwards.jar');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async downloadViaRewind(pluginsDir: string): Promise<void> {
|
|
133
|
+
const destPath = path.join(pluginsDir, 'ViaRewind.jar');
|
|
134
|
+
|
|
135
|
+
if (await FileUtils.fileExists(destPath)) {
|
|
136
|
+
await fs.remove(destPath);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await this.downloadFile(this.VIAREWIND_URL, destPath, 'ViaRewind.jar');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async verifyAllPlugins(pluginsDir: string): Promise<void> {
|
|
143
|
+
const files = ['ViaVersion.jar', 'ViaBackwards.jar', 'ViaRewind.jar'];
|
|
144
|
+
let allValid = true;
|
|
145
|
+
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
const filePath = path.join(pluginsDir, file);
|
|
148
|
+
|
|
149
|
+
if (!await FileUtils.fileExists(filePath)) {
|
|
150
|
+
this.logger.error(`${file} is missing!`);
|
|
151
|
+
allValid = false;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const stats = await fs.stat(filePath);
|
|
157
|
+
if (stats.size < 100000) {
|
|
158
|
+
this.logger.error(`${file} is too small: ${stats.size} bytes`);
|
|
159
|
+
allValid = false;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const buffer = await fs.readFile(filePath);
|
|
164
|
+
if (buffer[0] !== 0x50 || buffer[1] !== 0x4B) {
|
|
165
|
+
this.logger.error(`${file} has invalid ZIP header`);
|
|
166
|
+
allValid = false;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.logger.debug(`${file} verified OK (${(stats.size / 1024 / 1024).toFixed(2)} MB)`);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this.logger.error(`Failed to verify ${file}:`, error);
|
|
173
|
+
allValid = false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!allValid) {
|
|
178
|
+
throw new Error('Some ViaVersion plugins are corrupt or missing');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public async configureViaVersion(config: MinecraftConfig): Promise<void> {
|
|
183
|
+
const pluginsDir = config.folders.plugins;
|
|
184
|
+
const viaConfigDir = path.join(pluginsDir, 'ViaVersion');
|
|
185
|
+
await FileUtils.ensureDir(viaConfigDir);
|
|
186
|
+
|
|
187
|
+
const configFile = path.join(viaConfigDir, 'config.yml');
|
|
188
|
+
|
|
189
|
+
const configContent = `# ViaVersion Configuration
|
|
190
|
+
# Auto-generated by mc-headless
|
|
191
|
+
|
|
192
|
+
enable-client-side-block-updates: true
|
|
193
|
+
prevent-collision: true
|
|
194
|
+
auto-team: true
|
|
195
|
+
suppress-metadata-errors: false
|
|
196
|
+
enable-legacy-server-ping: true
|
|
197
|
+
block-connection-method: true
|
|
198
|
+
nms-player-ticking: true
|
|
199
|
+
debug: false
|
|
200
|
+
max-pps: 800
|
|
201
|
+
max-pp-interval: 4
|
|
202
|
+
fix-self-rendering: true
|
|
203
|
+
fix-low-level-collision: true
|
|
204
|
+
shield-blocking: true
|
|
205
|
+
fix-infested-block-breaking: true
|
|
206
|
+
ignore-long-1_16-channel: true
|
|
207
|
+
force-json-transform: false
|
|
208
|
+
use-natives: true
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
await FileUtils.writeFile(configFile, configContent);
|
|
212
|
+
this.logger.debug('ViaVersion configuration created');
|
|
213
|
+
}
|
|
214
|
+
}
|