quake 3 | list noises in a quake 3 map | list shaders in quake 3 shader | Search

This code analyzes Quake map files to extract and list the textures used within them.

Run example

npm run import -- "list textures in quake 3 map"

list textures in quake 3 map

var fs = require('fs')
var path = require('path')
var importer = require('../Core')
var {BitStream} = importer.import("bit buffer")

var MAX_QPATH = 64

var dshader_t = function () {
	this.shaderName   = null;                              // byte * MAX_QPATH (string)
	this.surfaceFlags = 0;                                 // int32
	this.contents     = 0;                                 // int32
};
dshader_t.size = 72;

function loadShaders(buffer, fileofs, filelen) {
	var bb = new BitStream(buffer);
	bb.byteIndex = fileofs;

	var shaders = new Array(filelen / dshader_t.size);

	for (var i = 0; i < shaders.length; i++) {
		var shader = shaders[i] = new dshader_t();

		shader.shaderName = bb.readASCIIString(MAX_QPATH);
		shader.surfaceFlags = bb.readInt32();
		shader.contents = bb.readInt32();
	}
    return shaders
}

function listTextures(file, dirsOnly) {

    if(file.includes('.bsp')) {
        file = fs.readFileSync(file)
        let bb = new BitStream(file);
        bb.readASCIIString(4);
        bb.readInt32();
        // lump 0 
        bb.readInt32()
        bb.readInt32()
        // lump 1 shaders
        let fileofs = bb.readInt32()
        let filelen = bb.readInt32()
        let shaders = loadShaders(file, fileofs, filelen)
        return shaders.filter((s, i, arr) => s.shaderName && arr.indexOf(s) == i).map(s => s.shaderName)
    }

    if(typeof file === 'string' && fs.existsSync(file)) {
        file = fs.readFileSync(file).toString('utf-8')
    }

    var used = []
    var brushes = importer.regexToArray(/\{[\s\S^\}\{]*?\}/ig, file, -1) // returns indexes
    console.log(`found ${brushes.length} brushes.`)
    for(var i = 0; i < brushes.length; i++) {
        var b = file.substr(brushes[i][0], brushes[i][1])
        var texts = importer.regexToArray(/\)\s+([^\)\(]*?)\s+((\s*[0-9\.-]+){5,8})/igm, b, 1)
        texts.forEach($1 => {
            if(dirsOnly) {
                if(!used.includes(path.dirname($1)))
                    used.push(path.dirname($1))
            } else {
                if(!used.includes($1))
                    used.push($1)
            }
        })
    }
    return used
}

module.exports = listTextures

What the code could have been:

// Import required modules
const fs = require('fs');
const path = require('path');
const { importer } = require('../Core');
const { BitStream } = importer.import('bit buffer');

// Define constants
const MAX_QPATH = 64;
const MAX_SHADER_SIZE = 72;

// Define shader structure
class Shader {
  /**
   * @param {string} shaderName - Shader name
   * @param {number} surfaceFlags - Surface flags
   * @param {number} contents - Contents
   */
  constructor(shaderName = null, surfaceFlags = 0, contents = 0) {
    this.shaderName = shaderName;
    this.surfaceFlags = surfaceFlags;
    this.contents = contents;
  }
}

// Define function to load shaders from binary file
function loadShaders(file, fileofs, filelen) {
  // TODO: Add error handling for file I/O operations
  const bb = new BitStream(file);
  bb.byteIndex = fileofs;

  // Initialize array to store shaders
  const shaders = new Array(filelen / MAX_SHADER_SIZE);

  // Iterate over each shader in the file
  for (let i = 0; i < shaders.length; i++) {
    // Create a new shader object
    const shader = shaders[i] = new Shader();

    // Read shader name, surface flags, and contents from the file
    shader.shaderName = bb.readASCIIString(MAX_QPATH);
    shader.surfaceFlags = bb.readInt32();
    shader.contents = bb.readInt32();
  }

  return shaders;
}

// Define function to list textures from a BSP file
function listTextures(file, dirsOnly = false) {
  // Check if the file is a BSP file
  if (file.includes('.bsp')) {
    // Read the BSP file into a buffer
    const fileBuffer = fs.readFileSync(file);

    // Initialize a BitStream object to read from the file buffer
    const bb = new BitStream(fileBuffer);

    // Skip over the BSP file header
    bb.readASCIIString(4);
    bb.readInt32();
    bb.readInt32();
    bb.readInt32();
    bb.readInt32();

    // Get the file offset and length for the shaders lump
    const fileofs = bb.readInt32();
    const filelen = bb.readInt32();

    // Load shaders from the file
    const shaders = loadShaders(fileBuffer, fileofs, filelen);

    // Return a list of unique shader names
    return shaders.filter((s, i, arr) => s.shaderName && arr.indexOf(s) === i).map(s => s.shaderName);
  }

  // Check if the file is a string
  if (typeof file ==='string') {
    // Check if the file exists on disk
    if (!fs.existsSync(file)) {
      throw new Error(`File not found: ${file}`);
    }

    // Read the file contents into a string
    const fileContents = fs.readFileSync(file, 'utf-8');
  }

  // Initialize an array to store used texture paths
  const usedTextures = [];

  // Use regular expressions to find all brushes in the file contents
  const brushes = importer.regexToArray(/\{[\s\S^\}\{]*?\}/ig, fileContents, -1);

  // Log the number of brushes found
  console.log(`Found ${brushes.length} brushes.`);

  // Iterate over each brush
  for (let i = 0; i < brushes.length; i++) {
    // Extract the brush contents
    const brush = fileContents.substr(brushes[i][0], brushes[i][1]);

    // Use regular expressions to find all texture paths in the brush
    const texturePaths = importer.regexToArray(/\)\s+([^\)\(]*?)\s+((\s*[0-9\.-]+){5,8})/igm, brush, 1);

    // Iterate over each texture path
    texturePaths.forEach((texturePath) => {
      // Check if the dirsOnly flag is set
      if (dirsOnly) {
        // Check if the texture path's directory has already been added to the usedTextures array
        if (!usedTextures.includes(path.dirname(texturePath))) {
          // Add the directory to the usedTextures array
          usedTextures.push(path.dirname(texturePath));
        }
      } else {
        // Check if the texture path has already been added to the usedTextures array
        if (!usedTextures.includes(texturePath)) {
          // Add the texture path to the usedTextures array
          usedTextures.push(texturePath);
        }
      }
    });
  }

  // Return the usedTextures array
  return usedTextures;
}

// Export the listTextures function
module.exports = listTextures;

This code snippet analyzes Quake map files to extract and list texture references used within the map.

Here's a breakdown:

  1. Initialization:

  2. loadShaders Function:

  3. listTextures Function:

Purpose:

This code likely serves as a tool for analyzing Quake map files and identifying the textures used within them. It can be used to generate lists of textures, identify missing textures, or analyze texture usage patterns.