Skip to content

Commit b5ccc54

Browse files
committed
Fix race condition in mod asset extraction
1 parent 93b03a2 commit b5ccc54

1 file changed

Lines changed: 37 additions & 44 deletions

File tree

lib/modloader/ModLoader.ts

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { exec } from 'node:child_process';
33
import { createWriteStream } from 'node:fs';
44
import * as fs from 'node:fs';
55
import * as Path from 'node:path';
6-
import { dirname, join, sep } from 'node:path';
6+
import { dirname, join } from 'node:path';
77
import { promisify } from 'node:util';
88
import { ncp } from 'ncp';
99
import fetch from 'node-fetch';
@@ -333,42 +333,47 @@ export class ModLoader {
333333
openZip(modFile, { lazyEntries: true, autoClose: true }, (e, f) => {
334334
if (e) {
335335
reject(e);
336+
} else {
337+
resolve(f);
336338
}
337-
resolve(f);
338339
});
339340
});
340341

341-
zipFile.readEntry();
342-
zipFile.on('error', e => process.stdout.write(String(e)));
343-
zipFile.on('entry', (entry: Entry) => {
344-
if (entry.fileName.endsWith('/')) {
345-
// Directory
346-
zipFile.readEntry();
347-
} else if (entry.fileName.startsWith('assets/') || entry.fileName.startsWith('data/')) {
348-
// File
349-
const prefix = entry.fileName.startsWith('assets/') ? 7 : 5;
350-
const targetFile = join(
351-
this.path,
352-
'mod_assets',
353-
entry.fileName.slice(prefix, entry.fileName.length),
354-
);
355-
const targetDir = dirname(targetFile);
356-
void this.ensureDirExists(targetDir).then(() => {
357-
zipFile.openReadStream(entry, (e, readStream) => {
358-
if (e) {
359-
throw e;
360-
}
361-
readStream.pipe(createWriteStream(targetFile));
362-
readStream.on('end', () => zipFile.readEntry());
363-
});
364-
}).catch((e: unknown) => {
365-
throw e;
366-
});
367-
} else {
368-
zipFile.readEntry();
369-
}
342+
await new Promise<void>((resolve, reject) => {
343+
zipFile.on('error', reject);
344+
zipFile.on('end', resolve);
345+
zipFile.on('entry', (entry: Entry) => {
346+
if (entry.fileName.endsWith('/')) {
347+
// Directory
348+
zipFile.readEntry();
349+
} else if (entry.fileName.startsWith('assets/') || entry.fileName.startsWith('data/')) {
350+
// File
351+
const prefix = entry.fileName.startsWith('assets/') ? 7 : 5;
352+
const targetFile = join(
353+
this.path,
354+
'mod_assets',
355+
entry.fileName.slice(prefix, entry.fileName.length),
356+
);
357+
const targetDir = dirname(targetFile);
358+
fs.promises.mkdir(targetDir, { recursive: true }).then(() => {
359+
zipFile.openReadStream(entry, (e, readStream) => {
360+
if (e) {
361+
reject(e);
362+
return;
363+
}
364+
const writeStream = createWriteStream(targetFile);
365+
readStream.on('error', reject);
366+
writeStream.on('error', reject);
367+
readStream.pipe(writeStream);
368+
writeStream.on('finish', () => zipFile.readEntry());
369+
});
370+
}).catch(reject);
371+
} else {
372+
zipFile.readEntry();
373+
}
374+
});
375+
zipFile.readEntry();
370376
});
371-
await new Promise(resolve => zipFile.on('end', resolve));
372377
}
373378

374379
/**
@@ -393,18 +398,6 @@ export class ModLoader {
393398
public async removeMods(): Promise<void> {
394399
await promisify(rimraf)(join(this.path, 'mods'));
395400
}
396-
397-
protected async ensureDirExists(path: string): Promise<void> {
398-
const segments = path.slice(this.path.length, this.path.length + path.length).split(sep);
399-
for (let i = 1; i <= segments.length; i++) {
400-
const subPath = join(this.path, segments.slice(0, i).join(sep));
401-
try {
402-
await fs.promises.stat(subPath);
403-
} catch {
404-
await fs.promises.mkdir(subPath);
405-
}
406-
}
407-
}
408401
}
409402

410403
export interface IModLoaderArgs {

0 commit comments

Comments
 (0)