Skip to content

Commit

Permalink
add ExtractZipServiceWorker, update vite config to build ExtractZipSe…
Browse files Browse the repository at this point in the history
…rvice to separate file
  • Loading branch information
BrettCleary committed Mar 10, 2025
1 parent 8451368 commit 734f286
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 29 deletions.
2 changes: 0 additions & 2 deletions src/backend/ipcHandlers/mods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,6 @@ export async function prepareBaseGameForModding({
extractService.on('canceled', () => {
logInfo(`Canceled Extracting of base game file`, LogPrefix.HyperPlay)

process.noAsar = false

cancelQueueExtraction()
callAbortController(appName)

Expand Down
47 changes: 41 additions & 6 deletions src/backend/services/ExtractZipService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { EventEmitter } from 'node:events'
import { Readable } from 'node:stream'
import { open, ZipFile, Entry } from 'yauzl'
import { mkdirSync, createWriteStream, rmSync, existsSync } from 'graceful-fs'
import { captureException } from '@sentry/electron'
import { join } from 'path'
import { extractOptions, ExtractZipServiceCommand } from './types'
import { isMainThread, parentPort } from 'node:worker_threads'

export interface ExtractZipProgressResponse {
/** Percentage of extraction progress. */
Expand All @@ -22,10 +23,6 @@ enum ExtractionValidation {
VALID = 'VALID'
}

type extractOptions = {
deleteOnEnd?: boolean
}

/**
* Service class to handle extraction of ZIP files.
* @extends {EventEmitter}
Expand Down Expand Up @@ -423,7 +420,6 @@ export class ExtractZipService extends EventEmitter {
return await this.#extractionPromise
} catch (error) {
this.#onError(error as Error)
captureException(error)

return false
} finally {
Expand All @@ -434,3 +430,42 @@ export class ExtractZipService extends EventEmitter {
}
}
}

let extractZipService: ExtractZipService | undefined = undefined
const eventsToListenTo = ['progress', 'finished', 'error', 'canceled']

if (!isMainThread) {
// disables electron's fs wrapper called when extracting .asar files
// which is necessary to extract electron app/game zip files
process.noAsar = true
parentPort?.on('message', (data) => {
const command: ExtractZipServiceCommand = data.type
switch (command) {
case 'INIT':
extractZipService = new ExtractZipService(
data.zipFile,
data.destinationPath,
data.options
)
for (const evName of eventsToListenTo) {
extractZipService.on(evName, (...args) => {
parentPort?.postMessage({ eventType: evName, args })
})
}
parentPort?.postMessage({ initEvent: 'ZIP_SERVICE_INSTANTIATED' })
break
case 'CANCEL':
extractZipService?.cancel()
break
case 'PAUSE':
extractZipService?.pause()
break
case 'EXTRACT':
extractZipService?.extract()
break
default:
break
}
})
parentPort?.postMessage({ initEvent: 'INITIALIZED' })
}
57 changes: 57 additions & 0 deletions src/backend/services/ExtractZipServiceWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Worker } from 'node:worker_threads'
import { extractOptions } from './types'
import { EventEmitter } from 'node:events'
import path from 'node:path'

export class ExtractZipServiceWorker extends EventEmitter {
worker: Worker
initPromise: Promise<void>
#resolveInitPromise: () => void = () => {
console.error('resolve init promise not assigned!')
}

constructor(
zipFile: string,
destinationPath: string,
options?: extractOptions
) {
super()
this.initPromise = new Promise((res) => {
this.#resolveInitPromise = res
})
const extractZipServicePath = path.join(
__dirname,
'../preload/ExtractZipService.js'
)
this.worker = new Worker(extractZipServicePath)

this.worker.on('message', (data) => {
if (data?.initEvent === 'INITIALIZED') {
this.worker.postMessage({
type: 'INIT',
zipFile,
destinationPath,
options
})
return
} else if (data?.initEvent === 'ZIP_SERVICE_INSTANTIATED') {
this.#resolveInitPromise()
return
}
const { eventType, args } = data
this.emit(eventType, ...args)
})
}

public cancel() {
this.worker.postMessage({ type: 'CANCEL' })
}

public pause(): void {
this.worker.postMessage({ type: 'PAUSE' })
}

async extract() {
this.worker.postMessage({ type: 'EXTRACT' })
}
}
5 changes: 5 additions & 0 deletions src/backend/services/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type extractOptions = {
deleteOnEnd?: boolean
}

export type ExtractZipServiceCommand = 'INIT' | 'CANCEL' | 'PAUSE' | 'EXTRACT'
26 changes: 6 additions & 20 deletions src/backend/storeManagers/hyperplay/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ import {
logInfo,
logWarning
} from 'backend/logger/logger'
import {
ExtractZipService,
ExtractZipProgressResponse
} from 'backend/services/ExtractZipService'
import { ExtractZipProgressResponse } from 'backend/services/ExtractZipService'
import {
existsSync,
mkdirSync,
Expand Down Expand Up @@ -102,6 +99,7 @@ import { ipfsGateway } from 'backend/vite_constants'
import { GlobalConfig } from 'backend/config'
import { PatchingError } from './types'
import { SiweMessage } from 'siwe'
import { ExtractZipServiceWorker } from 'backend/services/ExtractZipServiceWorker'

interface ProgressDownloadingItem {
DownloadItem: DownloadItem
Expand All @@ -114,7 +112,7 @@ interface ProgressDownloadingItem {
}

const inProgressDownloadsMap: Map<string, ProgressDownloadingItem> = new Map()
export const inProgressExtractionsMap: Map<string, ExtractZipService> =
export const inProgressExtractionsMap: Map<string, ExtractZipServiceWorker> =
new Map()

export async function getSettings(appName: string): Promise<GameSettings> {
Expand Down Expand Up @@ -764,8 +762,6 @@ export async function cancelExtraction(appName: string) {
)

try {
process.noAsar = false

const extractZipService = inProgressExtractionsMap.get(appName)
if (extractZipService) {
extractZipService.cancel()
Expand Down Expand Up @@ -963,8 +959,6 @@ export async function install(
}
return { status: 'done' }
} catch (error) {
process.noAsar = false

logInfo(
`Error while downloading and extracting game: ${error}`,
LogPrefix.HyperPlay
Expand Down Expand Up @@ -1052,10 +1046,6 @@ export async function extract(
const zipFile = path.join(directory, fileName)
logInfo(`Extracting ${zipFile} to ${destinationPath}`, LogPrefix.HyperPlay)

// disables electron's fs wrapper called when extracting .asar files
// which is necessary to extract electron app/game zip files
process.noAsar = true

sendFrontendMessage('gameStatusUpdate', {
appName,
status: 'extracting',
Expand All @@ -1078,7 +1068,8 @@ export async function extract(
}
})

const extractService = new ExtractZipService(zipFile, destinationPath)
const extractService = new ExtractZipServiceWorker(zipFile, destinationPath)
await extractService.initPromise

inProgressExtractionsMap.set(appName, extractService)

Expand Down Expand Up @@ -1154,8 +1145,6 @@ export async function extract(
status: 'extracting'
})

process.noAsar = false

if (isMac && executable.endsWith('.app')) {
const macAppExecutable = readdirSync(
join(executable, 'Contents', 'MacOS')
Expand Down Expand Up @@ -1192,6 +1181,7 @@ export async function extract(
)
extractService.once('error', (error: Error) => {
logError(`Extracting Error ${error.message}`, LogPrefix.HyperPlay)
captureException(error)

cancelQueueExtraction()
callAbortController(appName)
Expand All @@ -1210,8 +1200,6 @@ export async function extract(
LogPrefix.HyperPlay
)

process.noAsar = false

cancelQueueExtraction()
callAbortController(appName)

Expand Down Expand Up @@ -1254,8 +1242,6 @@ export async function extract(
extractService.extract().then()
})
} catch (error: unknown) {
process.noAsar = false

logInfo(`Error while extracting game ${error}`, LogPrefix.HyperPlay)

window.webContents.send('gameStatusUpdate', {
Expand Down
3 changes: 2 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ const preloads = [
'src/backend/proxy/providerPreload.ts',
'src/backend/hyperplay_store_preload.ts',
'src/backend/webview_style_preload.ts',
'src/backend/auth_provider_preload.ts'
'src/backend/auth_provider_preload.ts',
'src/backend/services/ExtractZipService.ts'
]

export default defineConfig(({ mode }) => ({
Expand Down

0 comments on commit 734f286

Please sign in to comment.