@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.
@@ -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: Partial<MinecraftConfig>) {
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
- await FileUtils.ensureDir(this.config.folders.plugins);
95
- await this.geyser.setup(this.config);
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
- 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/spigot`,
109
- fileName: 'geyser.jar'
110
- };
111
- } catch (error) {
112
- Downloader.logger.error(`Failed to get Geyser info: ${error}`);
113
- throw error;
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.20.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
+ }