quake 3 | Cell 5 | md4 checksum | Search

This code creates an index of Quake 3 maps stored in .pk3dir archives, listing each map and its associated files. It generates a structured JSON manifest for each archive, making it easier to manage and access the maps.

Run example

npm run import -- "make pk3 indexes"

make pk3 indexes

var fs = require('fs')
var os = require('os')
var path = require('path')
var glob = require('glob')

var DEFAULT_PATH = path.join(process.env.HOME || process.env.HOMEPATH 
  || process.env.USERPROFILE || os.tmpdir(), '/.quake3/bestmaps-cc')

async function makePk3MapIndex(searchPath, prefixPath) {
    if(typeof searchPath == 'undefined' || !searchPath) {
        searchPath = DEFAULT_PATH
    }
    if(typeof prefixPath == 'undefined') {
        prefixPath = '/base/baseq3-cc'
    } else if (!prefixPath) {
        prefixPath = '/'
    }
    // TODO: find files in dir based on alt-filetypes and names listed in pk3 file,
    //   so a combined directory of files, but separate indexes
    var pk3dirs = glob.sync('**/*.pk3dir/', {
        nodir: false, cwd: searchPath, nocase: true
    })
    var allMaps = {}
    pk3dirs.forEach(dir => {
        var pk3path = path.join(searchPath, dir)
        var pk3files = glob.sync('**/*', {
            nodir: false, cwd: pk3path, nocase: true
        })
        var maps = pk3files.filter(file => file.match(/\.bsp$/i))
        var pk3Key = path.join(prefixPath, dir).toLowerCase()
        var initial = {}
        allMaps[pk3Key] = initial[pk3Key] = {
            name: path.join('/', dir).replace(/\/$/ig, '')
        }
        var manifest = pk3files.map(file => {
            var stat = fs.statSync(path.join(searchPath, dir, file))
            return stat.isDirectory() ? ({
                name: path.join('/', dir, file).replace(/\/$/ig, ''),
            }) : ({
                name: path.join('/', dir, file),
                size: stat.size
            })
        }).reduce((obj, o) => {
            var key = path.join(prefixPath, o.name).toLowerCase()
                + (typeof o.size == 'undefined' ? '/' : '')
            obj[key] = o
            return obj
        }, initial)
        var manifestJson = JSON.stringify(manifest, null, 2)
        maps.forEach(map => {
            var mapName = path.basename(map).toLowerCase().replace(/\.bsp/i, '')
            var outIndexFile = path.join(searchPath, 'index-' + mapName + '.json')
            var key = path.join(prefixPath, path.basename(pk3path), map).toLowerCase()
            fs.writeFileSync(outIndexFile, manifestJson)
            allMaps[key] = {
                name: path.join('/', dir, map),
                size: fs.statSync(path.join(searchPath, dir, map)).size
            }
        })
    })
    return JSON.stringify(allMaps, null, 2)
}

module.exports = makePk3MapIndex

What the code could have been:

const fs = require('fs').promises;
const path = require('path');
const glob = require('glob');
const os = require('os');

const DEFAULT_PATH = path.join(
  process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE || os.tmpdir(),
  '/.quake3/bestmaps-cc'
);

/**
 * Creates a JSON index of maps from PK3 directories.
 * 
 * @param {string} searchPath - The path to search for PK3 directories (default: DEFAULT_PATH).
 * @param {string} prefixPath - The prefix to use for keys in the output JSON (default: '/base/baseq3-cc').
 * @returns {Promise<string>} A JSON string representing the map index.
 */
async function makePk3MapIndex(searchPath = DEFAULT_PATH, prefixPath = '/base/baseq3-cc') {
  // Resolve the search path
  searchPath = path.resolve(searchPath);

  // Find all PK3 directories
  const pk3Dirs = glob.sync('**/*.pk3dir/', {
    nodir: false,
    cwd: searchPath,
    nocase: true,
  });

  const allMaps = {};

  // Process each PK3 directory
  for (const dir of pk3Dirs) {
    // Get the absolute path of the PK3 directory
    const pk3Path = path.join(searchPath, dir);

    // Find all files in the PK3 directory
    const files = await glob('**/*', {
      nodir: false,
      cwd: pk3Path,
      nocase: true,
    });

    // Filter the files to only include bsp files
    const maps = files.filter(file => file.match(/\.bsp$/i));

    // Create a key for the PK3 directory in the output JSON
    const pk3Key = path.join(prefixPath, dir).toLowerCase();

    // Create an object to store the files in the PK3 directory
    const manifest = files.reduce((obj, file) => {
      const filePath = path.join(pk3Path, file);
      const stat = await fs.stat(filePath);
      const key = path.join(prefixPath, file).toLowerCase() + (stat.isDirectory()? '/' : '');
      obj[key] = {
        name: path.join('/', dir, file).replace(/\/$/ig, ''),
       ...(stat.isDirectory()? {} : { size: stat.size }),
      };
      return obj;
    }, {});

    // Create a JSON string for the manifest
    const manifestJson = JSON.stringify(manifest, null, 2);

    // Process each bsp file
    for (const map of maps) {
      // Get the name of the bsp file
      const mapName = path.basename(map).toLowerCase().replace(/\.bsp/i, '');

      // Create a key for the bsp file in the output JSON
      const mapKey = path.join(prefixPath, path.basename(pk3Path), map).toLowerCase();

      // Create an index file for the bsp file
      const indexFile = path.join(searchPath, `index-${mapName}.json`);
      await fs.writeFile(indexFile, manifestJson);

      // Add the bsp file to the output JSON
      allMaps[mapKey] = {
        name: path.join('/', dir, map),
        size: await fs.stat(path.join(searchPath, dir, map)).size,
      };
    }
  }

  // Return the output JSON as a string
  return JSON.stringify(allMaps, null, 2);
}

module.exports = makePk3MapIndex;

This code generates a map index for Quake 3 maps stored in .pk3dir archives.

Here's a breakdown:

  1. Initialization:

  2. Finding PK3 Directories:

  3. Processing Each PK3 Directory:

  4. Generating Manifest:

  5. Populating Map Index:

  6. Outputting Index:

In essence, this code automates the process of creating a structured index of Quake 3 maps stored in .pk3dir archives, making it easier to manage and access them.