UNPKG

@araxiaonline/mpq-tools-osx

Version:

Node.js wrapper for MPQ archive manipulation for MacOSX using mpqcli

263 lines (226 loc) 8.93 kB
const { execSync } = require('child_process'); const path = require('path'); const os = require('os'); class MPQTool { constructor(options = {}) { // Get platform-specific information const platform = os.platform(); const arch = os.arch(); // Use the pre-built binary for the current platform const binaryName = platform === 'win32' ? 'mpqcli.exe' : 'mpqcli'; const defaultPath = path.join(__dirname, '..', 'bin', `${platform}-${arch}`, binaryName); // Allow overriding the mpqcli path this.mpqcliPath = options.mpqcliPath || defaultPath; // Verify the binary exists if (!require('fs').existsSync(this.mpqcliPath)) { throw new Error(`mpqcli binary not found at ${this.mpqcliPath}. Make sure to run 'npm run build' first.`); } } /** * List all files in an MPQ archive * @param {string} mpqFile - Path to the MPQ file * @returns {string[]} Array of file paths in the archive */ listFiles(mpqFile) { try { const absolutePath = path.resolve(mpqFile); const cmd = `${this.mpqcliPath} list "${absolutePath}"`; const output = execSync(cmd, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 // 10MB buffer }); return output.split('\n').filter(line => line.trim()); } catch (error) { if (error.stdout) { // Even though it's an error, the command might have output return error.stdout.split('\n').filter(line => line.trim()); } throw new Error(`Failed to list files: ${error.message}`); } } /** * Extract all files from an MPQ archive * @param {string} mpqFile - Path to the MPQ file * @param {string} outputDir - Directory to extract files to */ extractAll(mpqFile, outputDir) { try { const fs = require('fs'); const absoluteMpqPath = path.resolve(mpqFile); const absoluteOutputDir = path.resolve(outputDir); // Create output directory if it doesn't exist fs.mkdirSync(absoluteOutputDir, { recursive: true }); // Extract files const cmd = `${this.mpqcliPath} extract -o "${absoluteOutputDir}" "${absoluteMpqPath}"`; execSync(cmd, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 // 10MB buffer }); // Fix path separators in extracted files const files = this.listFiles(mpqFile); files.forEach(file => { const extractedPath = path.join(absoluteOutputDir, file); const normalizedPath = path.join(absoluteOutputDir, file.replace(/\\/g, path.sep)); // Skip if paths are the same if (extractedPath === normalizedPath) return; // Create parent directory fs.mkdirSync(path.dirname(normalizedPath), { recursive: true }); // Move file to correct location if (fs.existsSync(extractedPath)) { fs.renameSync(extractedPath, normalizedPath); } }); } catch (error) { throw new Error(`Failed to extract archive: ${error.message}`); } } /** * Extract specific files from an MPQ archive * @param {string} mpqFile - Path to the MPQ file * @param {string} filePath - Path of the file within the archive to extract * @param {string} outputDir - Optional output directory * @returns {boolean} Success status */ extractFile(mpqFile, filePath, outputDir = null) { try { const absolutePath = path.resolve(mpqFile); const cmd = outputDir ? `${this.mpqcliPath} extract -o "${path.resolve(outputDir)}" -f "${filePath}" "${absolutePath}"` : `${this.mpqcliPath} extract -f "${filePath}" "${absolutePath}"`; execSync(cmd, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 // 10MB buffer }); return true; } catch (error) { throw new Error(`Failed to extract file: ${error.message}`); } } /** * Create a new MPQ archive from a directory * @param {string} directory - Directory to create MPQ from * @param {number} version - MPQ version (1 or 2) * @param {string} [outputPath] - Optional path for the MPQ file. If not provided, creates it next to the directory * @param {Object} [options] - Additional options * @param {boolean} [options.addFiles=false] - Whether to automatically add all files from the directory * @returns {string} Path to the created MPQ file */ createArchive(directory, version = 2, outputPath = null, options = { addFiles: false }) { try { const absoluteDir = path.resolve(directory); const dirName = path.basename(absoluteDir); const workingDir = path.dirname(absoluteDir); // Create the MPQ in the directory's parent const cmd = `${this.mpqcliPath} create "${dirName}" ${version === 2 ? '-v2' : ''}`; execSync(cmd, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, // 10MB buffer cwd: workingDir }); // MPQ is created as directory.mpq in the working directory const createdMpq = path.join(workingDir, dirName + '.mpq'); // Move to final destination if specified const finalMpqPath = outputPath ? path.resolve(outputPath) : createdMpq; if (outputPath) { const finalDir = path.dirname(finalMpqPath); // Create output directory if needed if (!require('fs').existsSync(finalDir)) { require('fs').mkdirSync(finalDir, { recursive: true }); } // Move the MPQ file require('fs').renameSync(createdMpq, finalMpqPath); } // Automatically add files if requested if (options.addFiles) { const fs = require('fs'); const self = this; function addFilesRecursively(dir, baseDir) { const files = fs.readdirSync(dir); files.forEach(file => { const fullPath = path.join(dir, file); const relativePath = path.relative(baseDir, fullPath); if (fs.statSync(fullPath).isDirectory()) { addFilesRecursively(fullPath, baseDir); } else { self.addFile(finalMpqPath, fullPath, relativePath); } }); } addFilesRecursively(absoluteDir, absoluteDir); } return finalMpqPath; } catch (error) { throw new Error(`Failed to create archive: ${error.message}`); } } /** * Helper method to recursively copy a directory * @private */ _copyDirectory(src, dest) { const fs = require('fs'); const path = require('path'); // Create destination directory fs.mkdirSync(dest, { recursive: true }); // Read directory contents const entries = fs.readdirSync(src, { withFileTypes: true }); for (const entry of entries) { const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { this._copyDirectory(srcPath, destPath); } else { fs.copyFileSync(srcPath, destPath); } } } /** * Search for files in an MPQ archive using a pattern * @param {string} mpqFile - Path to the MPQ file * @param {string} pattern - Search pattern * @returns {string[]} Array of matching file paths */ searchFiles(mpqFile, pattern) { try { const files = this.listFiles(mpqFile); return files.filter(file => file.toLowerCase().includes(pattern.toLowerCase())); } catch (error) { throw new Error(`Failed to search files: ${error.message}`); } } /** * Add files or directories to an existing MPQ archive * @param {string} mpqFile - Path to the MPQ file * @param {string} sourcePath - Path to the file or directory to add * @param {string} [targetPath] - Optional path within the MPQ where the file/directory should be placed * @returns {boolean} Success status */ addToArchive(mpqFile, sourcePath, targetPath = '') { try { const absoluteMpqPath = path.resolve(mpqFile); const absoluteSourcePath = path.resolve(sourcePath); let cmd = `${this.mpqcliPath} add "${absoluteMpqPath}" "${absoluteSourcePath}"`; if (targetPath) { cmd += ` "${targetPath}"`; } execSync(cmd, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 // 10MB buffer }); return true; } catch (error) { throw new Error(`Failed to add to archive: ${error.message}`); } } /** * Add a single file to an existing MPQ archive * @param {string} mpqFile - Path to the MPQ file * @param {string} filePath - Path to the file to add * @param {string} [targetPath] - Path within the MPQ where the file should be placed * @returns {boolean} Success status */ addFile(mpqFile, filePath, targetPath = null) { return this.addToArchive(mpqFile, filePath, targetPath || ''); } } module.exports = MPQTool;