Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8edb7a5
chore: Update deps
ci010 Feb 25, 2024
a3a26c8
BREAKING CHANGE: new download impl
ci010 Feb 25, 2024
d1a7903
refactor: Optimize the installer perf
ci010 Mar 3, 2024
0ca67d2
fix: Avoid conflict of DlibraryDirectory arg
ci010 Mar 3, 2024
5f45e48
fix: Correctly detect abort error
ci010 Mar 3, 2024
eb39df8
chore: Update version
ci010 Mar 3, 2024
6bf80f7
chore: fix argument pass with type
ci010 Mar 6, 2024
6ee65f9
fix: Try to avoid fail on false positive rename error
ci010 Mar 6, 2024
5169fa9
fix: The partial download does not work
ci010 Mar 12, 2024
fa78912
fix(file-transfer): Deny the 203 status code
ci010 Mar 13, 2024
abc1f16
fix: Should raise issue if file size is 0
ci010 Mar 13, 2024
643c317
fix: Use javaw instead of java
ci010 Mar 13, 2024
87b1476
fix: validator should not block download
ci010 Mar 15, 2024
69995bc
fix: Revert javaw change due to some incompatible situation
ci010 Mar 16, 2024
1191eb5
fix: Ensure data is synced before validate
ci010 Mar 20, 2024
e7303a1
fix: Should only respect the libraries host resolved result
ci010 Mar 20, 2024
8457cb4
refactor: Improve some error handling
ci010 Mar 20, 2024
329748c
chore: Update deps
ci010 Mar 29, 2024
a5b5d3f
chore: Update undici
ci010 Mar 29, 2024
7c7424d
refactor: Make dispatrcher of yggdrasil client optional
doabackflip Apr 5, 2024
c2d9244
chore: Update undici
ci010 Apr 7, 2024
905ce0b
fix: Wrong maven replacement
ci010 Apr 7, 2024
d724beb
fix: Some server does not follow range protocol. Add fallback behavio…
ci010 Apr 10, 2024
b2cb7f7
WIP
ci010 Apr 12, 2024
d289267
fix: Adjust curseforge fingerprint interface
ci010 Apr 15, 2024
b853c90
fix: Parse openjdk version
ci010 Apr 20, 2024
9e85d3a
chore: Update undici
ci010 Apr 22, 2024
114d382
fix: The wrong browser release config
ci010 Apr 25, 2024
0ac677c
docs: Upgrade node requirement
ci010 Apr 25, 2024
3663677
fix: wrong download url with querystring
ci010 May 27, 2024
aee9f8b
refactor: Ensure error is traced by stack
ci010 Jun 1, 2024
160c39c
fix: Update project type
ci010 Jun 14, 2024
f040473
fix: Should detect some special modpack logs
ci010 Jun 16, 2024
8cfb90f
feat: Support neoforge search type
ci010 Jun 16, 2024
cff0beb
chore: Update eslint
ci010 Jul 1, 2024
943b6ee
chore: Update eslint
ci010 Jul 2, 2024
600d1a5
refactor: Support server
ci010 Jul 3, 2024
299f1b3
fix: Should parse openjdk 1.8 version also
ci010 Jul 5, 2024
e0887b5
feat: add version server json getter
ci010 Jul 5, 2024
4bc9b0d
feat: Support install server for fabric and forge
ci010 Jul 5, 2024
53ad28a
fix: Support fabric/forge/neoforge/quilt server
ci010 Jul 8, 2024
972a95d
WIP
ci010 Jul 12, 2024
be9a60d
WIP
ci010 Jul 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Introduce how to setup environment, modify code and submit PR.

### Pre-requirements

- Node.js 16+
- Node.js >=18.17.0
- pnpm (can be setup by node [corepack](https://nodejs.org/api/corepack.html))

### Setup Dev Workspace
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
},
"packageManager": "[email protected]",
"devDependencies": {
"tsx": "^3.12.6",
"typedoc": "^0.24.8",
"typescript": "^5.0.3",
"tsx": "^4.7.1",
"typedoc": "^0.25.8",
"typescript": "^5.3.3",
"@vitest/coverage-c8": "^0.30.1",
"vitest": "^0.32.0"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/asm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@
"esbuild": "^0.17.16",
"eslint": "^8.37.0",
"tslib": "^2.5.0",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
}
}
2 changes: 1 addition & 1 deletion packages/bytebuffer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@
"esbuild": "^0.17.16",
"eslint": "^8.37.0",
"tslib": "^2.5.0",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
}
}
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@
"esbuild": "^0.17.16",
"eslint": "^8.37.0",
"tslib": "^2.5.0",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
}
}
5 changes: 5 additions & 0 deletions packages/core/diagnose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ export async function diagnoseFile<T extends string>({ file, expectedChecksum, r
receivedChecksum = await checksumFunc(file, algorithm)
if (signal?.aborted) return
issue = receivedChecksum !== expectedChecksum
} else {
const fstat = await stat(file)
if (fstat.size === 0) {
issue = true
}
}
const type = fileExisted ? 'corrupted' : 'missing' as const
if (issue) {
Expand Down
8 changes: 7 additions & 1 deletion packages/core/folder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ export class MinecraftFolder {
getNativesRoot(version: string) { return join(this.getVersionRoot(version), version + '-natives') }
getVersionRoot(version: string) { return join(this.versions, version) }
getVersionJson(version: string) { return join(this.getVersionRoot(version), version + '.json') }
getVersionJar(version: string, type?: string) { return type === 'client' || type === undefined ? join(this.getVersionRoot(version), version + '.jar') : join(this.getVersionRoot(version), `${version}-${type}.jar`) }
getVersionServerJson(version: string) { return join(this.getVersionRoot(version), 'server.json') }
getVersionJar(version: string, type?: string) {
if (type === 'client' || !type) return join(this.getVersionRoot(version), version + '.jar')
if (type === 'server') return this.getPath('libraries', 'net', 'minecraft', 'server', version, `server-${version}-bundled.jar`)
return join(this.getVersionRoot(version), version + `-${type}.jar`)
}

getVersionAll(version: string) {
return [
join(this.versions, version), join(this.versions, version, version + '.json'),
Expand Down
54 changes: 21 additions & 33 deletions packages/core/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,10 +393,6 @@ export interface BaseServerOptions {
* Java executable.
*/
javaPath: string
/**
* Current working directory. Default is the same with the path.
*/
cwd?: string
/**
* No gui for the server launch
*/
Expand All @@ -413,16 +409,6 @@ export interface BaseServerOptions {
spawn?: (command: string, args?: ReadonlyArray<string>, options?: SpawnOptions) => ChildProcess
}

export interface MinecraftServerOptions extends BaseServerOptions {
/**
* Minecraft location.
*/
path: string
/**
* The version id.
*/
version: string | ResolvedVersion
}
/**
* This is the case you provide the server jar execution path.
*/
Expand All @@ -432,18 +418,16 @@ export interface ServerOptions extends BaseServerOptions {
*
* This is the case like you are launching forge server.
*/
serverExectuableJarPath: string
serverExectuableJarPath?: string

mainClass?: string

classPath?: string[]
}

export async function launchServer(options: MinecraftServerOptions | ServerOptions) {
export async function launchServer(options: ServerOptions) {
const args = await generateArgumentsServer(options)
let cwd = options.cwd
if ('path' in options) {
cwd = options.path
} else {
cwd = dirname(options.serverExectuableJarPath)
}
const spawnOption = { cwd, env: process.env, ...(options.extraExecOption || {}) }
const spawnOption = { env: process.env, ...(options.extraExecOption || {}) }
return (options.spawn ?? spawn)(args[0], args.slice(1), spawnOption)
}

Expand Down Expand Up @@ -521,7 +505,7 @@ export function createMinecraftProcessWatcher(process: ChildProcess, emitter: Ev
} else if (string.indexOf('Crash report saved to:') !== -1) {
crashReportLocation = string.substring(string.indexOf('Crash report saved to:') + 'Crash report saved to: #@!@# '.length)
crashReportLocation = crashReportLocation.replace(EOL, '').trim()
} else if (waitForReady && (string.indexOf('Reloading ResourceManager') !== -1 || string.indexOf('LWJGL Version: ') !== -1 || string.indexOf('OpenAL initialized.') !== -1)) {
} else if (waitForReady && (string.indexOf('Missing metadata in pack') !== -1 || string.indexOf('Registering resource reload listener') !== -1 || string.indexOf('Reloading ResourceManager') !== -1 || string.indexOf('LWJGL Version: ') !== -1 || string.indexOf('OpenAL initialized.') !== -1)) {
waitForReady = false
emitter.emit('minecraft-window-ready')
}
Expand Down Expand Up @@ -577,21 +561,23 @@ export async function launch(options: LaunchOption): Promise<ChildProcess> {
/**
* Generate the argument for server
*/
export async function generateArgumentsServer(options: MinecraftServerOptions | ServerOptions) {
export async function generateArgumentsServer(options: ServerOptions) {
const { javaPath, minMemory = 1024, maxMemory = 1024, extraJVMArgs = [], extraMCArgs = [], extraExecOption = {} } = options
const cmd = [
javaPath,
`-Xms${(minMemory)}M`,
`-Xmx${(maxMemory)}M`,
...extraJVMArgs,
]
if ('path' in options) {
const mc = MinecraftFolder.from(options.path)
const version = options.version
const resolvedVersion = typeof version === 'string' ? await Version.parse(mc, version) : version
cmd.push('-jar', mc.getVersionJar(resolvedVersion.minecraftVersion, 'server'))
} else {

if (options.classPath && options.classPath.length > 0) {
cmd.push('-cp', options.classPath.join(delimiter))
}

if (options.serverExectuableJarPath) {
cmd.push('-jar', options.serverExectuableJarPath)
} else if (options.mainClass) {
cmd.push(options.mainClass)
}

cmd.push(...extraMCArgs)
Expand Down Expand Up @@ -681,8 +667,6 @@ export async function generateArguments(options: LaunchOption) {
}
}

cmd.push('-DlibraryDirectory=' + mc.getPath('libraries'))

const jvmOptions = {
natives_directory: nativeRoot,
launcher_name: launcherName,
Expand Down Expand Up @@ -711,6 +695,10 @@ export async function generateArguments(options: LaunchOption) {

cmd.push(...jvmArguments.map((arg) => format(arg, jvmOptions)))

if (!cmd.some(v => v.startsWith('-DlibraryDirectory'))) {
cmd.push('-DlibraryDirectory=' + mc.getPath('libraries'))
}

// add extra jvm args
if (options.extraJVMArgs instanceof Array) {
if (options.extraJVMArgs.some((v) => typeof v !== 'string')) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@
"@xmcl/eslint-config": "workspace:^*",
"esbuild": "^0.17.16",
"eslint": "^8.37.0",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
}
}
30 changes: 29 additions & 1 deletion packages/core/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,14 @@ export namespace Version {
checksums?: string[]
}

export type Library = NormalLibrary | NativeLibrary | PlatformSpecificLibrary | LegacyLibrary
export interface FlatLibrary {
name: string
url: string
sha1: string
size: number
}

export type Library = NormalLibrary | NativeLibrary | PlatformSpecificLibrary | LegacyLibrary | FlatLibrary

export type LaunchArgument = string | {
rules?: Rule[]
Expand Down Expand Up @@ -680,6 +687,19 @@ export namespace Version {
}
return new ResolvedLibrary(lib.name, info, lib.downloads.artifact)
}
// flat library
if ('url' in lib && 'sha1' in lib && 'size' in lib) {
if (!lib.url) {
throw new Error('Corrupted library: ' + JSON.stringify(lib))
}
return new ResolvedLibrary(lib.name, info, {
size: lib.size,
sha1: lib.sha1,
url: lib.url,
path: info.path,
})
}

const maven = lib.url || 'https://libraries.minecraft.net/'
const artifact: Artifact = {
size: -1,
Expand Down Expand Up @@ -856,4 +876,12 @@ export interface Version {
* NON CONVERSION! This only present in some third party launcher like Labymod to mark the real minecraft version
*/
_minecraftVersion?: string
/**
* NON CONVERSION! This only present in some third party launcher like Forge to mark the forge version
*/
_forgeVersion?: string
/**
* NON CONVERSION! This only present in some third party launcher like Fabric to mark the fabric version
*/
_fabricLoaderVersion?: string
}
75 changes: 75 additions & 0 deletions packages/curseforge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const enum FileModLoaderType {
LiteLoader = 3,
Fabric = 4,
Quilt = 5,
NeoForge = 6,
}
export interface FileIndex {
gameVersion: string
Expand Down Expand Up @@ -243,6 +244,8 @@ export interface File {

hashes: FileHash[]

fileFingerprint: number

/**
* The date of this file uploaded
*/
Expand Down Expand Up @@ -520,6 +523,38 @@ export interface CurseforgeClientOptions {
baseUrl?: string
}

export interface FingerprintMatch {
/**
* The mod id
*/
id: number
file: File
latestFiles: File[]
}
export interface FingerprintsMatchesResult {
data: {
isCacheBuilt: boolean
exactMatches: FingerprintMatch[]
exactFingerprints: number[]
partialMatches: FingerprintMatch[]
partialFingerprints: object
unmatchedFingerprints: number[]
}
}

export interface FingerprintFuzzyMatch {
id: number
file: File
latestFiles: File[]
fingerprints: number[]
}

export interface FingerprintFuzzyMatchResult {
data: {
fuzzyMatches: FingerprintFuzzyMatch[]
}
}

export class CurseforgeApiError extends Error {
constructor(readonly url: string, readonly status: number, readonly body: string) {
super(`Fail to fetch curseforge api ${url}. Status=${status}. ${body}`)
Expand Down Expand Up @@ -753,4 +788,44 @@ export class CurseforgeV1Client {
const result = await response.json() as { data: string }
return result.data
}

async getFingerprintsMatchesByGameId(gameId: number, fingerprints: number[], signal?: AbortSignal) {
const url = new URL(this.baseUrl + `/v1/fingerprints/${gameId}`)
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify({ fingerprints }),
headers: {
...this.headers,
'content-type': 'application/json',
accept: 'application/json',
},
dispatcher: this.dispatcher,
signal,
})
if (response.status !== 200) {
throw new CurseforgeApiError(url.toString(), response.status, await response.text())
}
const result = await response.json() as FingerprintsMatchesResult
return result.data
}

async getFingerprintsFuzzyMatchesByGameId(gameId: number, fingerprints: number[], signal?: AbortSignal) {
const url = new URL(this.baseUrl + `/v1/fingerprints/fuzzy/${gameId}`)
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify({ fingerprints }),
headers: {
...this.headers,
'content-type': 'application/json',
accept: 'application/json',
},
dispatcher: this.dispatcher,
signal,
})
if (response.status !== 200) {
throw new CurseforgeApiError(url.toString(), response.status, await response.text())
}
const result = await response.json() as FingerprintFuzzyMatchResult
return result.data
}
}
2 changes: 1 addition & 1 deletion packages/curseforge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
],
"license": "MIT",
"dependencies": {
"undici": "6.0.1"
"undici": "6.14.0"
},
"bugs": {
"url": "https://github.com/Voxelum/minecraft-launcher-core-node/issues"
Expand Down
4 changes: 2 additions & 2 deletions packages/discord-rpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@
"sideEffects": false,
"dependencies": {
"discord-api-types": "^0.37.39",
"undici": "6.0.1"
"undici": "6.14.0"
},
"devDependencies": {
"@types/node": "~18",
"eslint": "^8.37.0",
"@xmcl/eslint-config": "workspace:^*",
"esbuild": "^0.17.16",
"typescript": "^5.2.2",
"typescript": "^5.3.3",
"tsx": "^3.12.1"
},
"engines": {
Expand Down
8 changes: 4 additions & 4 deletions packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
"version": "0.0.1",
"description": "The xmcl commonly used eslint config for typescript",
"dependencies": {
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"eslint": "^8.37.0",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7.15.0",
"eslint": "^8.57.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^16.3.1",
"eslint-plugin-promise": "^6.1.1",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
}
}
6 changes: 0 additions & 6 deletions packages/file-transfer/abort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,3 @@ export function resolveAbortSignal(signal?: AbortSignal) {
removeEventListener() { return this },
}
}

export interface AbortSignal {
readonly aborted: boolean
addEventListener(event: string, handler: () => void): this
removeEventListener(event: string, handler: () => void): this
}
Loading
Loading