quake 3 | , find pk3 files in zips | list bsps in a pak | Search

This code automates the extraction of specific files, such as map files and screenshots, from Quake 3 PAK archives. It uses the node-stream-zip library to read the archives and extracts the desired files to specific locations.

Run example

npm run import -- "find only bsps in map paks"

find only bsps in map paks

var StreamZip = require('node-stream-zip');
var fs = require('fs');
var path = require('path');
var importer = require('../Core');
var {listInProject} = importer.import("list project files")


function readPak(zipFile, cb) {
    const zip = new StreamZip({
        file: zipFile,
        storeEntries: true
    });

    zip.on('ready', () => {
        console.log('Entries read: ' + zip.entriesCount + ' ' + path.basename(zipFile));
        for (const entry of Object.values(zip.entries())) {
            if(entry.name.includes('.bsp')) {
                console.log(entry.name)
                /*var pk3Path = '/Applications/ioquake3/bestmaps/' + path.basename(pk3.name);
                if(!fs.existsSync(pk3Path)) {
                    zip.extract(pk3.name, pk3Path, err => {
                        console.log((err ? 'Extract error ' : 'Extracted ') + pk3.name);
                        zip.close();
                        cb(zipFile);
                    });
                    return;
                }*/
            }
            if(entry.name.includes('levelshots/') && entry.name !== 'levelshots/') {
                var levelPath = '/Applications/ioquake3/bestmaps/levelshots/' + path.basename(entry.name)
                if(!fs.existsSync(levelPath)) {
                    zip.extract(entry.name, levelPath, err => {
                        console.log((err ? 'Extract error ' : 'Extracted ') + entry.name);
                        zip.close();
                        cb(zipFile);
                    });
                    return;
                }
            }
        }
        zip.close()
        cb();
    });
    
    zip.on('error', err => { cb() });
}


function extractPaks() {
    var results = listInProject('/Applications/ioquake3/bestmaps', '**/*.pk3')
    console.log(results)
    return importer.runAllPromises(results.map(r => {
        return (resolve) => {
            try {
                readPak(r, resolve)
            } catch (e) {
                if(e.message.includes('Bad archive')) {}
                else { console.log(e) }
                resolve()
            }
        }
    })).then(results => results.filter(r => r))
}


module.exports = extractPaks;

What the code could have been:

const { promises: fsPromises } = require('fs');
const path = require('path');
const { listInProject } = require('../Core');
const StreamZip = require('node-stream-zip');

/**
 * Function to read a pak file.
 * 
 * @param {string} zipFile - The path to the zip file.
 * @param {function} cb - The callback function to call when the operation is complete.
 */
async function readPak(zipFile) {
    try {
        const zip = new StreamZip({
            file: zipFile,
            storeEntries: true
        });

        await new Promise((resolve, reject) => {
            zip.on('ready', () => {
                resolve();
            });
            zip.on('error', (err) => {
                reject(err);
            });
        });

        console.log(`Entries read: ${zip.entriesCount} ${path.basename(zipFile)}`);
        const entries = Object.values(zip.entries());

        for (const entry of entries) {
            if (entry.name.includes('.bsp')) {
                console.log(entry.name);
            }
            if (entry.name.includes('levelshots/') && entry.name!== 'levelshots/') {
                const levelPath = path.join('/Applications/ioquake3/bestmaps', 'levelshots', path.basename(entry.name));
                if (!fsPromises.existsSync(levelPath)) {
                    zip.extract(entry.name, levelPath);
                }
            }
        }

        await new Promise((resolve) => {
            zip.on('close', () => {
                resolve();
            });
        });
    } catch (err) {
        console.log(err);
    } finally {
        return zipFile;
    }
}

/**
 * Function to extract paks.
 * 
 * @returns {Promise<string[]>} A promise that resolves with an array of extracted pak files.
 */
async function extractPaks() {
    const results = await listInProject('/Applications/ioquake3/bestmaps', '**/*.pk3');
    const extracted = await Promise.all(results.map(async (r) => {
        try {
            await readPak(r);
            return true;
        } catch (e) {
            if (e.message.includes('Bad archive')) {
                return false;
            } else {
                console.log(e);
                return true;
            }
        }
    }));
    return results.filter((_, index) => extracted[index]);
}

module.exports = extractPaks;

This code snippet is designed to extract files from Quake 3 PAK (package) archives.

Here's a breakdown:

  1. Dependencies:

  2. readPak Function:

  3. extractPaks Function:

In essence, this code automates the process of extracting specific files from Quake 3 PAK archives, likely for use in a map editing or modding tool.