@bobschlowinskii/clicraft 0.4.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/CHANGELOG.md +191 -0
- package/CONTRIBUTING.md +261 -0
- package/LICENSE.md +7 -0
- package/README.md +55 -0
- package/commands/auth.js +427 -0
- package/commands/config.js +356 -0
- package/commands/create.js +534 -0
- package/commands/info.js +113 -0
- package/commands/install.js +130 -0
- package/commands/launch.js +296 -0
- package/commands/search.js +50 -0
- package/commands/uninstall.js +94 -0
- package/commands/upgrade.js +343 -0
- package/commands/version.js +21 -0
- package/docs/README.md +119 -0
- package/docs/_config.yml +63 -0
- package/docs/commands/auth.md +376 -0
- package/docs/commands/config.md +294 -0
- package/docs/commands/create.md +276 -0
- package/docs/commands/info.md +460 -0
- package/docs/commands/install.md +320 -0
- package/docs/commands/launch.md +427 -0
- package/docs/commands/search.md +276 -0
- package/docs/commands/uninstall.md +128 -0
- package/docs/commands/upgrade.md +515 -0
- package/docs/commands.md +266 -0
- package/docs/configuration.md +410 -0
- package/docs/contributing.md +114 -0
- package/docs/index.md +82 -0
- package/docs/installation.md +162 -0
- package/helpers/auth/index.js +20 -0
- package/helpers/auth/microsoft.js +329 -0
- package/helpers/auth/storage.js +260 -0
- package/helpers/config.js +258 -0
- package/helpers/constants.js +38 -0
- package/helpers/getv.js +5 -0
- package/helpers/minecraft.js +308 -0
- package/helpers/modrinth.js +141 -0
- package/helpers/utils.js +334 -0
- package/helpers/versions.js +21 -0
- package/index.js +89 -0
- package/package.json +30 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getConfigDir } from '../config.js';
|
|
4
|
+
|
|
5
|
+
// Auth directory for storing multiple accounts
|
|
6
|
+
const CONFIG_DIR = getConfigDir();
|
|
7
|
+
const AUTH_DIR = path.join(CONFIG_DIR, 'auth');
|
|
8
|
+
const ACCOUNTS_FILE = path.join(AUTH_DIR, 'accounts.json');
|
|
9
|
+
|
|
10
|
+
// Ensure auth directory exists
|
|
11
|
+
function ensureAuthDir() {
|
|
12
|
+
if (!fs.existsSync(AUTH_DIR)) {
|
|
13
|
+
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load all accounts data
|
|
19
|
+
* @returns {{ accounts: Object.<string, AccountData>, currentAccount: string|null }}
|
|
20
|
+
*/
|
|
21
|
+
export function loadAccounts() {
|
|
22
|
+
ensureAuthDir();
|
|
23
|
+
if (fs.existsSync(ACCOUNTS_FILE)) {
|
|
24
|
+
try {
|
|
25
|
+
const data = JSON.parse(fs.readFileSync(ACCOUNTS_FILE, 'utf-8'));
|
|
26
|
+
return {
|
|
27
|
+
accounts: data.accounts || {},
|
|
28
|
+
currentAccount: data.currentAccount || null
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return { accounts: {}, currentAccount: null };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { accounts: {}, currentAccount: null };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Save accounts data
|
|
39
|
+
* @param {{ accounts: Object, currentAccount: string|null }} data
|
|
40
|
+
*/
|
|
41
|
+
export function saveAccounts(data) {
|
|
42
|
+
ensureAuthDir();
|
|
43
|
+
fs.writeFileSync(ACCOUNTS_FILE, JSON.stringify(data, null, 2));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the currently active account
|
|
48
|
+
* @returns {AccountData|null}
|
|
49
|
+
*/
|
|
50
|
+
export function getCurrentAccount() {
|
|
51
|
+
const data = loadAccounts();
|
|
52
|
+
if (data.currentAccount && data.accounts[data.currentAccount]) {
|
|
53
|
+
return data.accounts[data.currentAccount];
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get account by UUID or username
|
|
60
|
+
* @param {string} identifier - UUID or username
|
|
61
|
+
* @returns {AccountData|null}
|
|
62
|
+
*/
|
|
63
|
+
export function getAccount(identifier) {
|
|
64
|
+
const data = loadAccounts();
|
|
65
|
+
const lowerIdentifier = identifier.toLowerCase();
|
|
66
|
+
|
|
67
|
+
// Try UUID first
|
|
68
|
+
if (data.accounts[identifier]) {
|
|
69
|
+
return data.accounts[identifier];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Try username (case-insensitive)
|
|
73
|
+
for (const [uuid, account] of Object.entries(data.accounts)) {
|
|
74
|
+
if (account.username.toLowerCase() === lowerIdentifier) {
|
|
75
|
+
return account;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Add or update an account
|
|
84
|
+
* @param {AccountData} accountData
|
|
85
|
+
* @param {boolean} setAsCurrent - Whether to set as current account
|
|
86
|
+
*/
|
|
87
|
+
export function addAccount(accountData, setAsCurrent = true) {
|
|
88
|
+
const data = loadAccounts();
|
|
89
|
+
data.accounts[accountData.uuid] = accountData;
|
|
90
|
+
|
|
91
|
+
if (setAsCurrent || !data.currentAccount) {
|
|
92
|
+
data.currentAccount = accountData.uuid;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
saveAccounts(data);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Remove an account
|
|
100
|
+
* @param {string} identifier - UUID or username
|
|
101
|
+
* @returns {boolean} - Whether the account was removed
|
|
102
|
+
*/
|
|
103
|
+
export function removeAccount(identifier) {
|
|
104
|
+
const data = loadAccounts();
|
|
105
|
+
const lowerIdentifier = identifier.toLowerCase();
|
|
106
|
+
|
|
107
|
+
let uuidToRemove = null;
|
|
108
|
+
|
|
109
|
+
// Find by UUID
|
|
110
|
+
if (data.accounts[identifier]) {
|
|
111
|
+
uuidToRemove = identifier;
|
|
112
|
+
} else {
|
|
113
|
+
// Find by username
|
|
114
|
+
for (const [uuid, account] of Object.entries(data.accounts)) {
|
|
115
|
+
if (account.username.toLowerCase() === lowerIdentifier) {
|
|
116
|
+
uuidToRemove = uuid;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!uuidToRemove) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const removedAccount = data.accounts[uuidToRemove];
|
|
127
|
+
delete data.accounts[uuidToRemove];
|
|
128
|
+
|
|
129
|
+
// If we removed the current account, switch to another one
|
|
130
|
+
if (data.currentAccount === uuidToRemove) {
|
|
131
|
+
const remaining = Object.keys(data.accounts);
|
|
132
|
+
data.currentAccount = remaining.length > 0 ? remaining[0] : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
saveAccounts(data);
|
|
136
|
+
return removedAccount;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Switch to a different account
|
|
141
|
+
* @param {string} identifier - UUID or username
|
|
142
|
+
* @returns {AccountData|null} - The account switched to, or null if not found
|
|
143
|
+
*/
|
|
144
|
+
export function switchAccount(identifier) {
|
|
145
|
+
const data = loadAccounts();
|
|
146
|
+
const lowerIdentifier = identifier.toLowerCase();
|
|
147
|
+
|
|
148
|
+
let targetUuid = null;
|
|
149
|
+
|
|
150
|
+
// Find by UUID
|
|
151
|
+
if (data.accounts[identifier]) {
|
|
152
|
+
targetUuid = identifier;
|
|
153
|
+
} else {
|
|
154
|
+
// Find by username
|
|
155
|
+
for (const [uuid, account] of Object.entries(data.accounts)) {
|
|
156
|
+
if (account.username.toLowerCase() === lowerIdentifier) {
|
|
157
|
+
targetUuid = uuid;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!targetUuid) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
data.currentAccount = targetUuid;
|
|
168
|
+
saveAccounts(data);
|
|
169
|
+
|
|
170
|
+
return data.accounts[targetUuid];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get all accounts
|
|
175
|
+
* @returns {AccountData[]}
|
|
176
|
+
*/
|
|
177
|
+
export function getAllAccounts() {
|
|
178
|
+
const data = loadAccounts();
|
|
179
|
+
return Object.values(data.accounts);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get account count
|
|
184
|
+
* @returns {number}
|
|
185
|
+
*/
|
|
186
|
+
export function getAccountCount() {
|
|
187
|
+
const data = loadAccounts();
|
|
188
|
+
return Object.keys(data.accounts).length;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if an account exists
|
|
193
|
+
* @param {string} identifier - UUID or username
|
|
194
|
+
* @returns {boolean}
|
|
195
|
+
*/
|
|
196
|
+
export function accountExists(identifier) {
|
|
197
|
+
return getAccount(identifier) !== null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Update account data (e.g., refresh token)
|
|
202
|
+
* @param {string} uuid
|
|
203
|
+
* @param {Partial<AccountData>} updates
|
|
204
|
+
*/
|
|
205
|
+
export function updateAccount(uuid, updates) {
|
|
206
|
+
const data = loadAccounts();
|
|
207
|
+
if (data.accounts[uuid]) {
|
|
208
|
+
data.accounts[uuid] = { ...data.accounts[uuid], ...updates };
|
|
209
|
+
saveAccounts(data);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Migrate from old auth.json format
|
|
215
|
+
*/
|
|
216
|
+
export function migrateFromLegacy() {
|
|
217
|
+
const legacyFile = path.join(CONFIG_DIR, 'auth.json');
|
|
218
|
+
|
|
219
|
+
if (fs.existsSync(legacyFile)) {
|
|
220
|
+
try {
|
|
221
|
+
const legacyAuth = JSON.parse(fs.readFileSync(legacyFile, 'utf-8'));
|
|
222
|
+
|
|
223
|
+
if (legacyAuth.uuid && legacyAuth.username) {
|
|
224
|
+
const data = loadAccounts();
|
|
225
|
+
|
|
226
|
+
// Only migrate if not already present
|
|
227
|
+
if (!data.accounts[legacyAuth.uuid]) {
|
|
228
|
+
data.accounts[legacyAuth.uuid] = legacyAuth;
|
|
229
|
+
data.currentAccount = legacyAuth.uuid;
|
|
230
|
+
saveAccounts(data);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Backup and remove legacy file
|
|
234
|
+
const backupFile = path.join(CONFIG_DIR, 'auth.json.backup');
|
|
235
|
+
fs.renameSync(legacyFile, backupFile);
|
|
236
|
+
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
// Ignore migration errors
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export default {
|
|
248
|
+
loadAccounts,
|
|
249
|
+
saveAccounts,
|
|
250
|
+
getCurrentAccount,
|
|
251
|
+
getAccount,
|
|
252
|
+
addAccount,
|
|
253
|
+
removeAccount,
|
|
254
|
+
switchAccount,
|
|
255
|
+
getAllAccounts,
|
|
256
|
+
getAccountCount,
|
|
257
|
+
accountExists,
|
|
258
|
+
updateAccount,
|
|
259
|
+
migrateFromLegacy
|
|
260
|
+
};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
// Get the .clicraft config directory path
|
|
6
|
+
export function getConfigDir() {
|
|
7
|
+
const configDir = path.join(os.homedir(), '.clicraft');
|
|
8
|
+
if (!fs.existsSync(configDir)) {
|
|
9
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
return configDir;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Default settings
|
|
15
|
+
const DEFAULT_SETTINGS = {
|
|
16
|
+
// to implement in the future
|
|
17
|
+
|
|
18
|
+
// Default Java path (null = auto-detect)
|
|
19
|
+
// javaPath: null,
|
|
20
|
+
// Default memory allocation
|
|
21
|
+
// minMemory: '1G',
|
|
22
|
+
// maxMemory: '2G',
|
|
23
|
+
|
|
24
|
+
checkUpdates: true,
|
|
25
|
+
autoSaveToConfig: true,
|
|
26
|
+
autoLoadConfigOnLaunch: true,
|
|
27
|
+
settingsVersion: '0.3.0',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Default game settings to apply to new instances
|
|
31
|
+
// These are Minecraft options.txt settings
|
|
32
|
+
const DEFAULT_GAME_SETTINGS = {
|
|
33
|
+
// Common defaults - users can customize in ~/.clicraft/default-game-settings.json
|
|
34
|
+
// renderDistance: 12,
|
|
35
|
+
// fov: 70,
|
|
36
|
+
// guiScale: 0,
|
|
37
|
+
// gamma: 0.5,
|
|
38
|
+
// maxFps: 120,
|
|
39
|
+
// lang: 'en_us'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Default list of game settings to ignore when sharing configs
|
|
43
|
+
// These are typically user-specific or machine-specific
|
|
44
|
+
const DEFAULT_GAME_SETTINGS_IGNORE = [
|
|
45
|
+
// Window/display settings (machine-specific)
|
|
46
|
+
'fullscreen',
|
|
47
|
+
'overrideWidth',
|
|
48
|
+
'overrideHeight',
|
|
49
|
+
'fullscreenResolution',
|
|
50
|
+
// Account/identity (user-specific)
|
|
51
|
+
'lastServer',
|
|
52
|
+
'resourcePacks',
|
|
53
|
+
'incompatibleResourcePacks',
|
|
54
|
+
// Keybinds (often personal preference, can cause issues)
|
|
55
|
+
'key_*',
|
|
56
|
+
// Accessibility (personal)
|
|
57
|
+
'narrator',
|
|
58
|
+
'highContrast',
|
|
59
|
+
// Telemetry
|
|
60
|
+
'telemetryOptInExtra',
|
|
61
|
+
'onboardAccessibility'
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
// Load global settings from ~/.clicraft/settings.json
|
|
67
|
+
export function loadSettings() {
|
|
68
|
+
const configDir = getConfigDir();
|
|
69
|
+
const settingsPath = path.join(configDir, 'settings.json');
|
|
70
|
+
|
|
71
|
+
if (!fs.existsSync(settingsPath)) {
|
|
72
|
+
// Create default settings file
|
|
73
|
+
saveSettings(DEFAULT_SETTINGS);
|
|
74
|
+
return { ...DEFAULT_SETTINGS };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const content = fs.readFileSync(settingsPath, 'utf-8');
|
|
79
|
+
const userSettings = JSON.parse(content);
|
|
80
|
+
// Merge with defaults to ensure all keys exist
|
|
81
|
+
return { ...DEFAULT_SETTINGS, ...userSettings };
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error('Warning: Could not parse settings.json, using defaults');
|
|
84
|
+
return { ...DEFAULT_SETTINGS };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Save global settings to ~/.clicraft/settings.json
|
|
89
|
+
export function saveSettings(settings) {
|
|
90
|
+
const configDir = getConfigDir();
|
|
91
|
+
const settingsPath = path.join(configDir, 'settings.json');
|
|
92
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Load the game settings ignore list from ~/.clicraft/game-settings-ignore.json
|
|
96
|
+
export function loadGameSettingsIgnore() {
|
|
97
|
+
const configDir = getConfigDir();
|
|
98
|
+
const ignorePath = path.join(configDir, 'game-settings-ignore.json');
|
|
99
|
+
|
|
100
|
+
if (!fs.existsSync(ignorePath)) {
|
|
101
|
+
// Create default ignore file
|
|
102
|
+
saveGameSettingsIgnore(DEFAULT_GAME_SETTINGS_IGNORE);
|
|
103
|
+
return [...DEFAULT_GAME_SETTINGS_IGNORE];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const content = fs.readFileSync(ignorePath, 'utf-8');
|
|
108
|
+
return JSON.parse(content);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.error('Warning: Could not parse game-settings-ignore.json, using defaults');
|
|
111
|
+
return [...DEFAULT_GAME_SETTINGS_IGNORE];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Save the game settings ignore list
|
|
116
|
+
export function saveGameSettingsIgnore(ignoreList) {
|
|
117
|
+
const configDir = getConfigDir();
|
|
118
|
+
const ignorePath = path.join(configDir, 'game-settings-ignore.json');
|
|
119
|
+
fs.writeFileSync(ignorePath, JSON.stringify(ignoreList, null, 2));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Load default game settings from ~/.clicraft/default-game-settings.json
|
|
123
|
+
export function loadDefaultGameSettings() {
|
|
124
|
+
const configDir = getConfigDir();
|
|
125
|
+
const defaultsPath = path.join(configDir, 'default-game-settings.json');
|
|
126
|
+
|
|
127
|
+
if (!fs.existsSync(defaultsPath)) {
|
|
128
|
+
// Create default file with commented example
|
|
129
|
+
saveDefaultGameSettings(DEFAULT_GAME_SETTINGS);
|
|
130
|
+
return { ...DEFAULT_GAME_SETTINGS };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const content = fs.readFileSync(defaultsPath, 'utf-8');
|
|
135
|
+
return JSON.parse(content);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error('Warning: Could not parse default-game-settings.json, using defaults');
|
|
138
|
+
return { ...DEFAULT_GAME_SETTINGS };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Save default game settings
|
|
143
|
+
export function saveDefaultGameSettings(settings) {
|
|
144
|
+
const configDir = getConfigDir();
|
|
145
|
+
const defaultsPath = path.join(configDir, 'default-game-settings.json');
|
|
146
|
+
fs.writeFileSync(defaultsPath, JSON.stringify(settings, null, 2));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if a setting key matches the ignore list (supports wildcards)
|
|
150
|
+
export function isSettingIgnored(key, ignoreList) {
|
|
151
|
+
for (const pattern of ignoreList) {
|
|
152
|
+
if (pattern.endsWith('*')) {
|
|
153
|
+
// Wildcard match
|
|
154
|
+
const prefix = pattern.slice(0, -1);
|
|
155
|
+
if (key.startsWith(prefix)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
} else if (key === pattern) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Parse Minecraft options.txt into an object
|
|
166
|
+
export function parseOptionsTxt(content) {
|
|
167
|
+
const options = {};
|
|
168
|
+
const lines = content.split('\n');
|
|
169
|
+
|
|
170
|
+
for (const line of lines) {
|
|
171
|
+
const trimmed = line.trim();
|
|
172
|
+
if (!trimmed || !trimmed.includes(':')) continue;
|
|
173
|
+
|
|
174
|
+
const colonIndex = trimmed.indexOf(':');
|
|
175
|
+
const key = trimmed.substring(0, colonIndex);
|
|
176
|
+
const value = trimmed.substring(colonIndex + 1);
|
|
177
|
+
|
|
178
|
+
// Try to parse as number or boolean
|
|
179
|
+
if (value === 'true') {
|
|
180
|
+
options[key] = true;
|
|
181
|
+
} else if (value === 'false') {
|
|
182
|
+
options[key] = false;
|
|
183
|
+
} else if (!isNaN(value) && value !== '') {
|
|
184
|
+
options[key] = parseFloat(value);
|
|
185
|
+
} else {
|
|
186
|
+
options[key] = value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return options;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Generate options.txt content from an object
|
|
194
|
+
export function generateOptionsTxt(options) {
|
|
195
|
+
const lines = [];
|
|
196
|
+
|
|
197
|
+
for (const [key, value] of Object.entries(options)) {
|
|
198
|
+
lines.push(`${key}:${value}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return lines.join('\n');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Extract game settings from options.txt, filtering out ignored settings
|
|
205
|
+
export function extractGameSettings(optionsTxtPath) {
|
|
206
|
+
if (!fs.existsSync(optionsTxtPath)) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const content = fs.readFileSync(optionsTxtPath, 'utf-8');
|
|
211
|
+
const allOptions = parseOptionsTxt(content);
|
|
212
|
+
const ignoreList = loadGameSettingsIgnore();
|
|
213
|
+
|
|
214
|
+
const filteredOptions = {};
|
|
215
|
+
for (const [key, value] of Object.entries(allOptions)) {
|
|
216
|
+
if (!isSettingIgnored(key, ignoreList)) {
|
|
217
|
+
filteredOptions[key] = value;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return filteredOptions;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Write game settings to options.txt in an instance
|
|
225
|
+
export function writeGameSettings(instancePath, gameSettings) {
|
|
226
|
+
if (!gameSettings || Object.keys(gameSettings).length === 0) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const optionsPath = path.join(instancePath, 'options.txt');
|
|
231
|
+
|
|
232
|
+
// If options.txt already exists, merge with existing settings
|
|
233
|
+
let existingOptions = {};
|
|
234
|
+
if (fs.existsSync(optionsPath)) {
|
|
235
|
+
const content = fs.readFileSync(optionsPath, 'utf-8');
|
|
236
|
+
existingOptions = parseOptionsTxt(content);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Merge: gameSettings values override existing
|
|
240
|
+
const mergedOptions = { ...existingOptions, ...gameSettings };
|
|
241
|
+
|
|
242
|
+
fs.writeFileSync(optionsPath, generateOptionsTxt(mergedOptions));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default {
|
|
246
|
+
getConfigDir,
|
|
247
|
+
loadSettings,
|
|
248
|
+
saveSettings,
|
|
249
|
+
loadGameSettingsIgnore,
|
|
250
|
+
saveGameSettingsIgnore,
|
|
251
|
+
loadDefaultGameSettings,
|
|
252
|
+
saveDefaultGameSettings,
|
|
253
|
+
isSettingIgnored,
|
|
254
|
+
parseOptionsTxt,
|
|
255
|
+
generateOptionsTxt,
|
|
256
|
+
extractGameSettings,
|
|
257
|
+
writeGameSettings
|
|
258
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import version from './getv.js';
|
|
2
|
+
|
|
3
|
+
// User agent for all API requests
|
|
4
|
+
export const USER_AGENT = `clicraft/${version} (https://github.com/theinfamousben/clicraft)`;
|
|
5
|
+
|
|
6
|
+
// Modrinth API base URL
|
|
7
|
+
export const MODRINTH_API = 'https://api.modrinth.com/v2';
|
|
8
|
+
|
|
9
|
+
// Mojang/Minecraft APIs
|
|
10
|
+
export const MOJANG_VERSION_MANIFEST = 'https://launchermeta.mojang.com/mc/game/version_manifest.json';
|
|
11
|
+
export const MOJANG_RESOURCES = 'https://resources.download.minecraft.net';
|
|
12
|
+
|
|
13
|
+
// Fabric APIs
|
|
14
|
+
export const FABRIC_META = 'https://meta.fabricmc.net/v2';
|
|
15
|
+
export const FABRIC_MAVEN = 'https://maven.fabricmc.net';
|
|
16
|
+
|
|
17
|
+
// Forge APIs
|
|
18
|
+
export const FORGE_MAVEN = 'https://maven.minecraftforge.net';
|
|
19
|
+
export const FORGE_PROMOS = 'https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json';
|
|
20
|
+
|
|
21
|
+
// Pagination constants
|
|
22
|
+
export const PAGE_SIZE = 10;
|
|
23
|
+
export const NEXT_PAGE = '__NEXT_PAGE__';
|
|
24
|
+
export const PREV_PAGE = '__PREV_PAGE__';
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
USER_AGENT,
|
|
28
|
+
MODRINTH_API,
|
|
29
|
+
MOJANG_VERSION_MANIFEST,
|
|
30
|
+
MOJANG_RESOURCES,
|
|
31
|
+
FABRIC_META,
|
|
32
|
+
FABRIC_MAVEN,
|
|
33
|
+
FORGE_MAVEN,
|
|
34
|
+
FORGE_PROMOS,
|
|
35
|
+
PAGE_SIZE,
|
|
36
|
+
NEXT_PAGE,
|
|
37
|
+
PREV_PAGE
|
|
38
|
+
};
|