Files
yaze/docs/internal/archive/completed_features/wasm-patch-export-implementation.md

5.3 KiB

WASM Patch Export Documentation

Overview

The WASM patch export functionality allows users to export their ROM modifications as BPS or IPS patch files directly from the browser. This enables sharing modifications without distributing copyrighted ROM data.

Features

Supported Formats

BPS (Beat Patch Format)

  • Modern patch format with advanced features
  • Variable-length encoding for efficient storage
  • Delta encoding for changed regions
  • CRC32 checksums for validation
  • No size limitations
  • Better compression than IPS

IPS (International Patching System)

  • Classic patch format with wide compatibility
  • Simple record-based structure
  • RLE encoding for repeated bytes
  • Maximum file size of 16MB (24-bit addressing)
  • Widely supported by emulators and patching tools

API

#include "app/platform/wasm/wasm_patch_export.h"

// Export as BPS patch
absl::Status status = WasmPatchExport::ExportBPS(
    original_rom_data,  // std::vector<uint8_t>
    modified_rom_data,  // std::vector<uint8_t>
    "my_hack.bps"      // filename
);

// Export as IPS patch
absl::Status status = WasmPatchExport::ExportIPS(
    original_rom_data,
    modified_rom_data,
    "my_hack.ips"
);

// Get preview of changes
PatchInfo info = WasmPatchExport::GetPatchPreview(
    original_rom_data,
    modified_rom_data
);
// info.changed_bytes - total bytes changed
// info.num_regions - number of distinct regions
// info.changed_regions - vector of (offset, length) pairs

Implementation Details

BPS Format Structure

Header:
  - "BPS1" magic (4 bytes)
  - Source size (variable-length)
  - Target size (variable-length)
  - Metadata size (variable-length, 0 for no metadata)

Patch Data:
  - Actions encoded as variable-length integers
  - SourceRead: Copy from source (action = (length-1) << 2)
  - TargetRead: Copy from patch (action = ((length-1) << 2) | 1)

Footer:
  - Source CRC32 (4 bytes, little-endian)
  - Target CRC32 (4 bytes, little-endian)
  - Patch CRC32 (4 bytes, little-endian)

IPS Format Structure

Header:
  - "PATCH" (5 bytes)

Records (repeating):
  Normal Record:
    - Offset (3 bytes, big-endian)
    - Size (2 bytes, big-endian, non-zero)
    - Data (size bytes)

  RLE Record:
    - Offset (3 bytes, big-endian)
    - Size (2 bytes, always 0x0000)
    - Run length (2 bytes, big-endian)
    - Value (1 byte)

Footer:
  - "EOF" (3 bytes)

Browser Integration

The patch files are downloaded using the HTML5 Blob API:

  1. Patch data is generated in C++
  2. Data is passed to JavaScript via EM_JS
  3. JavaScript creates a Blob with the binary data
  4. Object URL is created from the Blob
  5. Hidden anchor element triggers download
  6. Cleanup occurs after download starts
// Simplified download flow
var blob = new Blob([patchData], { type: 'application/octet-stream' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);

Usage in Yaze Editor

Menu Integration

Add to MenuOrchestrator or RomFileManager:

if (ImGui::BeginMenu("File")) {
    if (ImGui::BeginMenu("Export", rom_->is_loaded())) {
        if (ImGui::MenuItem("Export BPS Patch...")) {
            ShowPatchExportDialog(PatchFormat::BPS);
        }
        if (ImGui::MenuItem("Export IPS Patch...")) {
            ShowPatchExportDialog(PatchFormat::IPS);
        }
        ImGui::EndMenu();
    }
    ImGui::EndMenu();
}

Tracking Original ROM State

To generate patches, the ROM class needs to track both original and modified states:

class Rom {
    std::vector<uint8_t> original_data_;  // Preserve original
    std::vector<uint8_t> data_;          // Working copy

public:
    void LoadFromFile(const std::string& filename) {
        // Load data...
        original_data_ = data_;  // Save original state
    }

    const std::vector<uint8_t>& original_data() const {
        return original_data_;
    }
};

Testing

Unit Tests

# Run patch export tests
./build/bin/yaze_test --gtest_filter="*WasmPatchExport*"

Manual Testing in Browser

  1. Build for WASM: emcc ... -s ENVIRONMENT=web
  2. Load a ROM in the web app
  3. Make modifications
  4. Use File → Export → Export BPS/IPS Patch
  5. Verify patch downloads correctly
  6. Test patch with external patching tool

Limitations

IPS Format

  • Maximum ROM size: 16MB (0xFFFFFF bytes)
  • No checksum validation
  • Less efficient compression than BPS
  • No metadata support

BPS Format

  • Requires more complex implementation
  • Less tool support than IPS
  • Larger patch size for small changes

Browser Constraints

  • Download triggered via user action only
  • No direct filesystem access
  • Patch must fit in browser memory
  • Download folder determined by browser

Error Handling

Common errors and solutions:

Error Cause Solution
Empty ROM data No ROM loaded Check rom->is_loaded() first
IPS size limit ROM > 16MB Use BPS format instead
No changes Original = Modified Show warning to user
Download failed Browser restrictions Ensure user-triggered action

Future Enhancements

Potential improvements:

  • UPS (Universal Patching Standard) support
  • Patch compression (zip/gzip)
  • Batch patch export
  • Patch preview/validation
  • Incremental patch generation
  • Patch metadata (author, description)
  • Direct patch sharing via URL