XPlor/CLAUDE.md
njohnson b844c71758 Add build and deploy scripts
- build_debug.cmd / build_release.cmd: Windows batch files with MSVC setup
- build_all_debug.sh / build_all_release.sh: Shell wrappers for Git Bash
- deploy.sh: Automated release script for Gitea
- Update CLAUDE.md with build instructions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 16:08:27 -05:00

9.0 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Build Instructions

To build the project, use the provided build scripts:

build_debug.cmd         # Debug build (Windows CMD)
build_release.cmd       # Release build (Windows CMD)

Or from Git Bash:

./build_all_debug.sh    # Debug build
./build_all_release.sh  # Release build

This project uses Qt 6.10.0 with MSVC 2022 on Windows. The build scripts use jom (Qt's parallel make) and automatically run windeployqt. The .cmd files set up the MSVC environment via vcvars64.bat.

Build output locations:

  • Debug: build/Debug/app/debug/app.exe
  • Release: build/Release/app/release/app.exe

Deployment

NEVER run deploy.sh without explicit user consent. Focus on building and testing only. The deploy script creates releases and uploads to Gitea - this should only be done when the user explicitly requests a release.

Project Overview

XPlor is a Qt-based binary file format explorer for video game files. It uses XScript, a custom DSL for defining binary file structures, allowing new formats to be added without modifying core code.

Architecture

XPlor/
├── app/                    # Qt GUI application (MainWindow, previews, settings)
├── libs/
│   ├── dsl/               # XScript engine (lexer, parser, interpreter, typeregistry)
│   ├── core/              # Utilities, logging, syntax highlighters
│   ├── compression/       # LZO, zlib, Oodle, xcompress64 support
│   └── encryption/        # Salsa20, SHA1 support
├── definitions/           # XScript format definitions
│   ├── cod/              # Call of Duty formats (60+ files)
│   ├── asura/            # Sniper Elite V2 formats
│   └── [other engines]
└── third_party/          # External SDKs (DevIL, Xbox SDK, zlib)

DSL Engine Flow

  1. TypeRegistry loads and merges .xscript files from definitions/
  2. Lexer tokenizes XScript source
  3. Parser builds AST
  4. Interpreter executes AST against binary streams
  5. Results stored as QVariantMap for UI display

XScript Language Reference

Type Definition

type typename ui("Display Name", root) byteorder LE
{
  criteria { require _ext == "ff"; }

  // Parse body with UI display
  magic = ascii(read(4)) ui("Magic");
  u32 version ui("Version");

  // Fields without ui() suffix are not shown in UI
  u32 internal_field;
}

Scalar Types

u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, bool

Built-in Functions

Reading:

  • read(n) - read n bytes
  • cstring() - read null-terminated string
  • wstring() / wstring_be() - read UTF-16 LE/BE string
  • skip(n) - skip n bytes
  • align(n) - align to n-byte boundary
  • seek(pos) - seek to absolute position
  • pos() / size() - current position / file size

Random Access:

  • u8at(off), u16at(off), u32at(off), u64at(off), i32at(off) - read at offset without advancing
  • bytesat(off, len) - read bytes at offset
  • with_seek(offset, expr) - evaluate expression at offset, restore position

Parsing:

  • parse("typename", bytes) - parse bytes as type (creates new stream)
  • parse_here("typename") - parse type at current position (same stream, delegation pattern)

Context:

  • ctx_set(name, value) - set global context variable (cross-type communication)
  • ctx_get(name) - get global context variable
  • set_name(value) - set item name for tree display
  • set_display(value) - set display name

Decompression:

  • zlib(bytes) - decompress zlib
  • xmem(bytes) - decompress Xbox xcompress
  • deflate(bytes) - decompress raw deflate

Conversion:

  • ascii(bytes) / utf8(bytes) / utf16le(bytes) / utf16be(bytes) - bytes to string
  • hex(value) - format as hex string

Collections:

  • make_list() / make_object() - create empty list/object
  • push(listName, value) - append to list
  • get(container, key) - get from list/object
  • set(varName, key, value) - set in list/object
  • len(value) - length of string/bytes/list

String:

  • contains(str, sub), starts_with(str, prefix), ends_with(str, suffix)
  • substr(str, start, len), split(str, delim), replace(str, old, new)
  • trim(str), to_lower(str), clean(str), basename(path)

UI Metadata (inline suffix syntax):

  • u32 field ui("Display Name"); - readonly field shown in UI
  • u32 field ui("Display Name", edit); - editable field shown in UI
  • var = expr ui("Display Name"); - assignment with UI display
  • set_viewer("hex"|"text"|"image"|"audio") - set viewer type
  • preview(data) / set_preview(filename, data) - set preview data

Control:

  • match(value, case1, result1, case2, result2, ..., default) - switch expression
  • bit(value, bit) / bit(value, start, count) - extract bits
  • check_criteria("typename") - test if type criteria matches current position

Control Flow

if (condition) { ... } else { ... }
while (condition) { ... }
repeat count { ... }  // provides _i index
for i in start..end { ... }
break;

// Match expression (replaces long if-else chains)
match(value) {
  "pattern1" => { ... }
  "pattern2" | "pattern3" => { ... }  // multiple patterns
  default => { ... }
}

Constants

const PTR_INLINE = -1;
const MAGIC_HEADER = 0x12345678;

Array Declaration (simplifies repeat/push patterns)

// Instead of: items = 0; repeat(count) { _x = parse_here("type"); items = push("items", _x); }
array[count] items: typename;

// With pointer condition:
array[count] items: typename when items_ptr;

Pipeline Syntax

zone = read(size) | zlib | parse("zone");

File Variables

  • _path - full file path
  • _name - filename
  • _basename - filename without extension
  • _ext - file extension (lowercase)

Key Files

  • libs/dsl/interpreter.cpp - Runtime execution, all built-in functions
  • libs/dsl/parser.cpp - AST generation
  • libs/dsl/typeregistry.cpp - Type loading and file detection
  • app/mainwindow.cpp - Main UI logic
  • definitions/cod/fastfile.xscript - Primary COD format (good example)

Conventions

  • Use ctx_set()/ctx_get() for cross-type context (not set_global/get_global)
  • Use skip(n) for padding, not _padding = read(n)
  • Pointer values of -1 indicate inline data follows
  • Use -1 consistently (not 4294967295 or 0xFFFFFFFF)

Byte Order Inheritance

Child types automatically inherit byte order from their parent when called via parse_here() or parse(). This eliminates the need for duplicate BE/LE type definitions in most cases.

Rules

  1. Root types must have explicit byteorder (needed for file detection criteria)
  2. Child types should omit byteorder to inherit from parent
  3. Only use explicit byteorder on child types when you need to override inherited order

Pattern: Thin Dispatcher + Shared Implementation

For formats that exist in both BE and LE variants with identical structure:

// Root dispatcher for BE files (Xbox 360, Wii)
type myformat ui("My Format", root) byteorder BE
{
  criteria { require ascii(bytesat(0, 4)) == "MAGIC"; }
  parse_here("myformat_impl");
}

// Root dispatcher for LE files (PC, original Xbox)
type myformat_le ui("My Format", root) byteorder LE
{
  criteria { require ascii(bytesat(0, 4)) == "CIGAM"; }
  parse_here("myformat_impl");
}

// Shared implementation - inherits byteorder from caller
type myformat_impl ui("My Format")
{
  magic = ascii(read(4)) ui("Magic");
  u32 version ui("Version");
  // ... all parsing logic once ...
}

Pattern: Runtime Detection with BE Wrapper

When a single root type detects endianness at runtime:

// Root dispatcher - uses LE, delegates to BE wrapper when needed
type myformat ui("My Format", root) byteorder LE
{
  criteria { require magic == 0xBEEFFEED || magic == 0xEDFEEFBE; }

  if (u32at(0) == 0xEDFEEFBE) {
    parse_here("myformat_be");  // BE wrapper overrides inherited LE
  } else {
    parse_here("myformat_impl");  // Inherits LE
  }
}

// BE wrapper - explicit byteorder overrides inherited LE
type myformat_be ui("My Format BE") byteorder BE
{
  parse_here("myformat_impl");
}

// Shared implementation - inherits byteorder from caller
type myformat_impl ui("My Format") { ... }

When NOT to Consolidate

Keep separate BE/LE types when:

  • Structures are legitimately different per platform (different header sizes, fields, etc.)
  • Magic bytes differ in ways that indicate structural differences (not just reversed)

Documentation Updates

IMPORTANT: After making changes to the XScript DSL (parser, interpreter, or language features), you MUST update the documentation:

  1. Update the LaTeX source: Edit docs/xscript-guide.tex with the new/changed syntax
  2. Rebuild documentation: Run docs/build-docs.cmd (Windows) or docs/build-docs.sh (Linux/Mac)
    • This generates xscript-guide.pdf and xscript-guide.md
    • Requires pdflatex and pandoc to be installed
  3. Update this file: Keep the XScript Language Reference section in CLAUDE.md in sync with major changes