Task 629: .RUN File Format

Task 629: .RUN File Format

.RUN File Format Specifications

The .RUN file format is not a standardized binary file format with a fixed specification like ELF or PE. Instead, it is a conventional format used primarily in Linux for self-extracting installers or archives. These files are typically generated using tools like "makeself" (a shell script utility) and consist of a shell script header concatenated with a compressed binary payload (e.g., a tar.gz archive). The header is executable text (starting with a shebang like #!/bin/sh), which handles extraction, integrity checks, and optional post-extraction actions. The payload is appended after the header.

.RUN files are executable (with the execute bit set in the file system), and when run, the script extracts the payload to a temporary directory, verifies checksums if present, and may execute a startup script from the archive. There is no magic number or strict binary header; identification relies on the shebang and script content. The format is flexible, but common in software distributions like NVIDIA drivers or other installers.

1. List of All Properties Intrinsic to This File Format

Based on the structure of makeself-generated .RUN files (the most common implementation), the following properties are embedded in the shell script header. These are set as shell variables and used for extraction logic, integrity verification, and configuration. They are "intrinsic" in that they define the file's behavior and content without relying on external file system metadata (e.g., permissions or timestamps, which are OS-dependent). The properties are parsed from the text header:

  • label: Descriptive name or title of the archive (e.g., "NVIDIA Driver Installer").
  • targetdir: Directory where the archive should be extracted (default: ".").
  • filesize: Expected uncompressed size of the archive in KB (used for disk space checks).
  • md5sum: MD5 checksum of the compressed payload for integrity verification.
  • sha256sum: SHA256 checksum of the compressed payload for integrity verification.
  • compression: Compression method for the payload (e.g., "gz" for gzip, "bz2" for bzip2, "zstd" for Zstandard).
  • script: Path to a startup script within the extracted archive to run after extraction.
  • scriptargs: Arguments to pass to the startup script.
  • encryption: Encryption method if the payload is encrypted (e.g., "gpg" for GPG-encrypted).
  • key: Encryption key or passphrase (if encryption is used).
  • signature: GPG signature for verification (if signed).
  • keep: Flag to keep extracted files after execution (yes/no).
  • noexec: Flag to prevent executing the startup script (yes/no).
  • quiet: Flag to suppress output during extraction (yes/no).
  • nox11: Flag to disable X11 checks (yes/no).
  • nodiskspace: Flag to skip disk space checks (yes/no).
  • follow: Flag to follow symlinks during extraction (yes/no).
  • skip: Line number offset to the start of the binary payload (calculated during creation; key to locating the archive).

These properties are set via shell assignments in the header (e.g., label="My Archive"). The header ends just before the binary payload, and the skip value indicates how many lines to skip to reach the payload.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .RUN File Dump

This is an HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post (or any HTML page). It allows dragging and dropping a .RUN file, parses the header to extract the properties, and dumps them to the screen. It uses FileReader to read the file as text (assuming the header is ASCII) and parses lines for variable assignments.

Drag and drop a .RUN file here

4. Python Class for .RUN File Handling

import os
import re

class RunFileHandler:
    def __init__(self, filename):
        self.filename = filename
        self.properties = {}
        self.skip = 0
        self.payload = b''

    def read(self):
        with open(self.filename, 'rb') as f:
            content = f.read()
            # Assume header is text until binary payload; split lines
            text_content = content.decode('utf-8', errors='ignore')
            lines = text_content.split('\n')
            for i, line in enumerate(lines):
                line = line.strip()
                if line.startswith('skip='):
                    self.skip = int(re.search(r'skip=(\d+)', line).group(1))
                    break
                match = re.match(r'(\w+)=["\']?(.*?)["\']?$', line)
                if match and not line.startswith('#'):
                    key, value = match.groups()
                    self.properties[key] = value
            # Extract payload starting after skip lines
            header_lines = '\n'.join(lines[:self.skip-1]).encode('utf-8')
            self.payload = content[len(header_lines) + (self.skip-1):]  # Account for newlines

    def print_properties(self):
        if not self.properties:
            print("No properties found. Call read() first.")
            return
        for key, value in self.properties.items():
            print(f"{key}: {value}")
        print(f"skip: {self.skip}")

    def write(self, new_filename, new_properties=None, new_payload=None):
        if new_properties:
            self.properties.update(new_properties)
        if new_payload:
            self.payload = new_payload
        header = '#!/bin/sh\n'  # Basic shebang
        for key, value in self.properties.items():
            header += f'{key}="{value}"\n'
        header += f'skip={len(header.splitlines()) + 1}\n'  # Update skip
        # Add extraction logic (simplified)
        header += 'tail -n +$skip $0 | gunzip | tar x\nexit 0\n'
        with open(new_filename, 'wb') as f:
            f.write(header.encode('utf-8'))
            f.write(self.payload)
        os.chmod(new_filename, 0o755)  # Make executable

# Example usage:
# handler = RunFileHandler('example.run')
# handler.read()
# handler.print_properties()
# handler.write('new.run', {'label': 'Updated Label'})

5. Java Class for .RUN File Handling

import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;

public class RunFileHandler {
    private String filename;
    private Map<String, String> properties = new HashMap<>();
    private int skip = 0;
    private byte[] payload;

    public RunFileHandler(String filename) {
        this.filename = filename;
    }

    public void read() throws IOException {
        byte[] content = Files.readAllBytes(Paths.get(filename));
        String textContent = new String(content, "UTF-8");  // Ignore errors for binary
        String[] lines = textContent.split("\n");
        Pattern assignPattern = Pattern.compile("(\\w+)=[\"']?(.*?)[\"']?$");
        for (String line : lines) {
            line = line.trim();
            if (line.startsWith("skip=")) {
                skip = Integer.parseInt(line.split("=")[1].trim());
                break;
            }
            Matcher matcher = assignPattern.matcher(line);
            if (matcher.matches() && !line.startsWith("#")) {
                properties.put(matcher.group(1), matcher.group(2));
            }
        }
        // Extract payload
        String headerText = String.join("\n", Arrays.copyOf(lines, skip - 1));
        int headerLength = headerText.getBytes("UTF-8").length + (skip - 1);  // Newlines
        payload = Arrays.copyOfRange(content, headerLength, content.length);
    }

    public void printProperties() {
        if (properties.isEmpty()) {
            System.out.println("No properties found. Call read() first.");
            return;
        }
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        System.out.println("skip: " + skip);
    }

    public void write(String newFilename, Map<String, String> newProperties, byte[] newPayload) throws IOException {
        if (newProperties != null) {
            properties.putAll(newProperties);
        }
        if (newPayload != null) {
            payload = newPayload;
        }
        StringBuilder header = new StringBuilder("#!/bin/sh\n");
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            header.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"\n");
        }
        String headerStr = header.toString();
        int newSkip = headerStr.split("\n").length + 1;
        headerStr += "skip=" + newSkip + "\n";
        headerStr += "tail -n +$skip $0 | gunzip | tar x\nexit 0\n";  // Simplified extraction
        Files.write(Paths.get(newFilename), headerStr.getBytes("UTF-8"));
        try (FileOutputStream fos = new FileOutputStream(newFilename, true)) {
            fos.write(payload);
        }
        new File(newFilename).setExecutable(true);
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     RunFileHandler handler = new RunFileHandler("example.run");
    //     handler.read();
    //     handler.printProperties();
    //     handler.write("new.run", Map.of("label", "Updated Label"), null);
    // }
}

6. JavaScript Class for .RUN File Handling

This is a Node.js class (requires fs module). Run with node script.js.

const fs = require('fs');

class RunFileHandler {
  constructor(filename) {
    this.filename = filename;
    this.properties = {};
    this.skip = 0;
    this.payload = Buffer.alloc(0);
  }

  read() {
    const content = fs.readFileSync(this.filename);
    const textContent = content.toString('utf8');  // Full as text, ignore binary errors
    const lines = textContent.split('\n');
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i].trim();
      if (line.startsWith('skip=')) {
        this.skip = parseInt(line.split('=')[1]);
        break;
      }
      const match = line.match(/(\w+)=["']?(.*?)["']?$/);
      if (match && !line.startsWith('#')) {
        this.properties[match[1]] = match[2];
      }
    }
    const headerLines = lines.slice(0, this.skip - 1).join('\n');
    const headerLength = Buffer.from(headerLines, 'utf8').length + (this.skip - 1);  // Newlines
    this.payload = content.slice(headerLength);
  }

  printProperties() {
    if (Object.keys(this.properties).length === 0) {
      console.log('No properties found. Call read() first.');
      return;
    }
    for (const [key, value] of Object.entries(this.properties)) {
      console.log(`${key}: ${value}`);
    }
    console.log(`skip: ${this.skip}`);
  }

  write(newFilename, newProperties = {}, newPayload = null) {
    if (Object.keys(newProperties).length > 0) {
      Object.assign(this.properties, newProperties);
    }
    if (newPayload) {
      this.payload = newPayload;
    }
    let header = '#!/bin/sh\n';
    for (const [key, value] of Object.entries(this.properties)) {
      header += `${key}="${value}"\n`;
    }
    const tempHeaderLines = header.split('\n').length + 1;  // For skip and extraction
    header += `skip=${tempHeaderLines + 2}\n`;  // Adjust for added lines
    header += 'tail -n +$skip $0 | gunzip | tar x\nexit 0\n';
    fs.writeFileSync(newFilename, header, 'utf8');
    fs.appendFileSync(newFilename, this.payload);
    fs.chmodSync(newFilename, 0o755);
  }
}

// Example usage:
// const handler = new RunFileHandler('example.run');
// handler.read();
// handler.printProperties();
// handler.write('new.run', { label: 'Updated Label' });

7. C++ Class for .RUN File Handling

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <regex>
#include <vector>
#include <sys/stat.h>  // For chmod

class RunFileHandler {
private:
    std::string filename;
    std::map<std::string, std::string> properties;
    int skip = 0;
    std::vector<char> payload;

public:
    RunFileHandler(const std::string& fn) : filename(fn) {}

    void read() {
        std::ifstream file(filename, std::ios::binary);
        if (!file) {
            std::cerr << "Failed to open file." << std::endl;
            return;
        }
        std::vector<char> content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        std::string textContent(content.begin(), content.end());
        std::vector<std::string> lines;
        std::string line;
        for (char c : textContent) {
            if (c == '\n') {
                lines.push_back(line);
                line.clear();
            } else {
                line += c;
            }
        }
        if (!line.empty()) lines.push_back(line);

        std::regex assignRegex(R"((\w+)=["']?(.*?)["']?$)");
        std::smatch match;
        for (const auto& ln : lines) {
            std::string trimmed = ln;
            // Trim whitespace (simplified)
            trimmed.erase(0, trimmed.find_first_not_of(" \t"));
            trimmed.erase(trimmed.find_last_not_of(" \t") + 1);
            if (trimmed.rfind("skip=", 0) == 0) {
                skip = std::stoi(trimmed.substr(5));
                break;
            }
            if (std::regex_match(trimmed, match, assignRegex) && trimmed[0] != '#') {
                properties[match[1]] = match[2];
            }
        }
        std::string headerText;
        for (int i = 0; i < skip - 1; ++i) {
            headerText += lines[i] + "\n";
        }
        size_t headerLength = headerText.size();
        payload.assign(content.begin() + headerLength, content.end());
    }

    void printProperties() const {
        if (properties.empty()) {
            std::cout << "No properties found. Call read() first." << std::endl;
            return;
        }
        for (const auto& pair : properties) {
            std::cout << pair.first << ": " << pair.second << std::endl;
        }
        std::cout << "skip: " << skip << std::endl;
    }

    void write(const std::string& newFilename, const std::map<std::string, std::string>& newProperties = {}, const std::vector<char>& newPayload = {}) {
        for (const auto& pair : newProperties) {
            properties[pair.first] = pair.second;
        }
        if (!newPayload.empty()) {
            payload = newPayload;
        }
        std::string header = "#!/bin/sh\n";
        for (const auto& pair : properties) {
            header += pair.first + "=\"" + pair.second + "\"\n";
        }
        std::vector<std::string> headerLines = split(header, '\n');
        int newSkip = headerLines.size() + 2;  // For skip and extraction lines
        header += "skip=" + std::to_string(newSkip) + "\n";
        header += "tail -n +$skip $0 | gunzip | tar x\nexit 0\n";

        std::ofstream outFile(newFilename, std::ios::binary);
        outFile << header;
        outFile.write(payload.data(), payload.size());
        outFile.close();
        chmod(newFilename.c_str(), 0755);
    }

private:
    std::vector<std::string> split(const std::string& s, char delimiter) {
        std::vector<std::string> tokens;
        std::string token;
        std::istringstream tokenStream(s);
        while (std::getline(tokenStream, token, delimiter)) {
            tokens.push_back(token);
        }
        return tokens;
    }
};

// Example usage:
// int main() {
//     RunFileHandler handler("example.run");
//     handler.read();
//     handler.printProperties();
//     handler.write("new.run");
//     return 0;
// }