Skip to content

Commit

Permalink
First client launched
Browse files Browse the repository at this point in the history
  • Loading branch information
GreenAppers committed Nov 6, 2024
1 parent 569615a commit e54b198
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 109 deletions.
29 changes: 0 additions & 29 deletions src/utils/downloader.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import axios from 'axios'
import { app } from 'electron'
import crypto from 'crypto'
import fs from 'fs'
import path from 'path'
import { v4 as uuidv4 } from 'uuid'

import { GameInstall } from '../constants'

export async function checkFileExists(path: string) {
try {
Expand Down Expand Up @@ -82,28 +78,3 @@ export async function downloadIfMissing(
await ensureDirectory(path.dirname(dest))
await download(url, dest)
}

export async function setupInstall(install: GameInstall) {
if (!install.name) {
throw new Error('Install name is required')
}
if (!install.versionManifest) {
throw new Error('Install version manifest is required')
}

if (!install.uuid) {
install.uuid = uuidv4()
}
if (!install.path) {
install.path = path.join(app.getPath('userData'), 'installs', install.uuid)
}
await ensureDirectory(install.path)

const versionJson = path.join(
install.path,
`${install.versionManifest.id}.json`
)
if (!(await checkFileExists(versionJson))) {
await download(install.versionManifest.url, versionJson)
}
}
128 changes: 48 additions & 80 deletions src/utils/launcher.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,35 @@
import { spawn } from 'child_process'
import { app } from 'electron'
import fs from 'fs'
import path from 'path'
import pSettle from 'p-settle'
import { v4 as uuidv4 } from 'uuid'

import {
GameInstall,
MojangRule,
MojangStringsTemplate,
mojangVersionDetails,
} from '../constants'
import { GameInstall, mojangVersionDetails } from '../constants'
import {
checkFileExists,
download,
downloadIfMissing,
ensureDirectory,
} from './downloader'
import {
allowRules,
applyArgumentsTemplate,
filterBlankArguments,
getOsArch,
getOsName,
} from './template'

const launchRunning: Record<string, GameInstall> = {}

const getLibrariesPath = () => path.join(app.getPath('userData'), 'libraries')
export const getLibrariesPath = () =>
path.join(app.getPath('userData'), 'libraries')

const getOsArch = () => {
switch (process.arch) {
case 'ia32':
return 'x86'
default:
return process.arch
}
}

const getOsName = () => {
switch (process.platform) {
case 'win32':
return 'windows'
case 'darwin':
return 'osx'
case 'linux':
return 'linux'
default:
return process.platform
}
}

const allowRules = (
context: { osName: string; osArch: string },
rules?: MojangRule[]
) => {
let include = true
for (const rule of rules ?? []) {
if (rule.action === 'allow') {
if (!rule.os?.name || rule.os.name === context.osName) continue
if (!rule.os?.arch || rule.os?.arch === context.osArch) continue
include = false
break
}
}
return include
}

const replaceTemplateVariables = (
input: string,
values: Record<string, string>
) => input.replace(/\${(\w+)}/g, (match, key) => values[key] ?? match)

const applyArgumentsTemplate = (
context: { osName: string; osArch: string },
template: MojangStringsTemplate,
values: Record<string, string>,
output: string[]
) => {
for (const argument of template) {
if (typeof argument === 'string') {
output.push(replaceTemplateVariables(argument, values))
continue
}
if (!allowRules(context, argument.rules)) continue
if (typeof argument.value === 'string')
output.push(replaceTemplateVariables(argument.value, values))
else
output.push(
...argument.value.map((x) => replaceTemplateVariables(x, values))
)
}
return output
}
export const getClientJarPath = (version: string) =>
path.join(
getLibrariesPath(),
`com/mojang/minecraft/${version}/minecraft-${version}-client.jar`
)

export async function setupInstall(install: GameInstall) {
if (!install.name) {
Expand Down Expand Up @@ -133,6 +78,13 @@ export async function updateInstall(install: GameInstall) {
)
)
}
downloadLibraries.push(() =>
downloadIfMissing(
versionDetails.downloads.client.url,
getClientJarPath(versionDetails.id),
versionDetails.downloads.client.sha1
)
)
await pSettle(downloadLibraries, { concurrency: 8 })

return versionDetails
Expand Down Expand Up @@ -161,14 +113,14 @@ export async function launchInstall(
version_name: install.versionManifest.id,
game_directory: install.path,
assets_root: '',
assets_index_name: '',
assets_index_name: '1.21',
auth_uuid: '',
auth_access_token: '',
auth_access_token: '0',
clientid: '',
auth_xuid: '',
user_type: '',
version_type: '',
natives_directory: '',
natives_directory: path.join(install.path, 'natives'),
launcher_name: 'Empire Utils',
launcher_version: '1.0.0',
classpath: '',
Expand All @@ -179,25 +131,41 @@ export async function launchInstall(
if (!allowRules({ osName, osArch }, library.rules)) continue
appendClasspath(path.join(librariesPath, library.downloads.artifact.path))
}
appendClasspath(getClientJarPath(versionDetails.id))

const command = ['java']
let command = ['java']
applyArgumentsTemplate(
{ osName, osArch },
versionDetails.arguments.game,
versionDetails.arguments.jvm,
template,
command
)
command.push(versionDetails.mainClass)
applyArgumentsTemplate(
{ osName, osArch },
versionDetails.arguments.jvm,
versionDetails.arguments.game,
template,
command
)
command.push(versionDetails.mainClass)

command = filterBlankArguments(command)
callback('Launching install: ' + command.join(' '))

callback('Complete')
const child = spawn(command[0], command.slice(1), {
cwd: install.path,
})
child.stdout.pipe(process.stdout)
child.stderr.pipe(process.stderr)
child.on('error', (err: Error) => {
throw new Error(`launch:${launchId} failed to start! ${err}`)
})
child.on('exit', (code: number) => {
if (code === 0) {
console.info(`launch:${launchId} exited`)
} else {
console.info(`launch:${launchId} exited with code ${code}`)
}
})
callback(`Complete: ${child.pid}`)
} catch (error) {
console.log('launchInstall error', error)
throw error
Expand Down
98 changes: 98 additions & 0 deletions src/utils/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { MojangRule, MojangStringsTemplate } from '../constants'

export const replaceTemplateVariables = (
input: string,
values: Record<string, string>
) => input.replace(/\${(\w+)}/g, (match, key) => values[key] ?? match)

export const getOsArch = () => {
switch (process.arch) {
case 'ia32':
return 'x86'
default:
return process.arch
}
}

export const getOsName = () => {
switch (process.platform) {
case 'win32':
return 'windows'
case 'darwin':
return 'osx'
case 'linux':
return 'linux'
default:
return process.platform
}
}

export const allowRules = (
context: {
osName: string
osArch: string
features?: Record<string, boolean>
},
rules?: MojangRule[]
) => {
let include = true
for (const rule of rules ?? []) {
if (rule.action === 'allow') {
if (rule.os?.name && rule.os.name !== context.osName) {
include = false
break
}
if (rule.os?.arch && rule.os?.arch !== context.osArch) {
include = false
break
}
const features = Object.entries(rule.features ?? {})
if (
features?.length &&
!features.every(([k, v]) => context.features?.[k] === v)
) {
include = false
break
}
}
}
return include
}

export const applyArgumentsTemplate = (
context: { osName: string; osArch: string },
template: MojangStringsTemplate,
values: Record<string, string>,
output: string[]
) => {
for (const argument of template) {
if (typeof argument === 'string') {
output.push(replaceTemplateVariables(argument, values))
continue
}
if (!allowRules(context, argument.rules)) continue
if (typeof argument.value === 'string')
output.push(replaceTemplateVariables(argument.value, values))
else
output.push(
...argument.value.map((x) => replaceTemplateVariables(x, values))
)
}
return output
}

export const filterBlankArguments = (input: string[]) => {
const output: string[] = []
for (let i = 0; i < input.length; i++) {
if (
i < input.length - 1 &&
input[i + 1] === '' &&
input[i].startsWith('--')
) {
i++
continue
}
output.push(input[i])
}
return output
}

0 comments on commit e54b198

Please sign in to comment.