Task 547: .PHZ File Format

Task 547: .PHZ File Format

File Format Specifications for .PHZ

The .PHZ file format is associated with Algodoo (formerly Phun), a 2D physics simulation software. It is essentially a ZIP archive with a .phz extension, used to package scene data. The archive typically contains:

  • A .phn file: The main scene data in text format, describing objects (e.g., circles, boxes, polygons, planes, springs, fixates, hinges, pens, groups) and their properties (e.g., position, size, color, velocity, density, friction, etc.).
  • thumbnail.png: A PNG image for the scene preview.
  • checksums.txt: A text file with checksums for verification.
  • Optional texture images: Additional PNG or other images used in the scene.

The format is not a custom binary structure but leverages the ZIP format for compression and packaging. "Decode" in this context means unzipping and parsing the contents, particularly the .phn text file.

  1. List of all the properties of this file format intrinsic to its file system:
  • Archive signature (PK\x03\x04 for local file header, PK\x05\x06 for end of central directory).
  • Number of files in the archive.
  • Total uncompressed size of the archive.
  • Total compressed size of the archive.
  • File names (e.g., "scene.phn", "thumbnail.png", "checksums.txt").
  • Compression method for each file (e.g., 0 for stored, 8 for deflated).
  • Last modification time and date for each file.
  • CRC-32 checksum for each file.
  • Uncompressed size for each file.
  • Compressed size for each file.
  • Extra field length for each file (if any).
  • File comment (if any).
  • Central directory offset.
  • End of central directory comment length.

These are the core ZIP format properties, as .PHZ is a renamed ZIP archive with specific content conventions for Algodoo scenes.

  1. Two direct download links for files of format .PHZ:

(Note: Itch.io generates download links on click, so visit https://leon1111.itch.io/algodoo-leon and click the download buttons for the .phz files.)

  1. Ghost blog embedded HTML JavaScript for drag and drop .PHZ file to dump properties:
PHZ File Dumper

Drag and Drop .PHZ File

Drop .PHZ file here

  

  1. Python class for .PHZ:
import zipfile
import os

class PHZHandler:
    def __init__(self, filename):
        self.filename = filename
        self.properties = {}

    def read(self):
        with zipfile.ZipFile(self.filename, 'r') as zf:
            self.properties['number_of_files'] = len(zf.namelist())
            total_compressed = 0
            total_uncompressed = 0
            for info in zf.infolist():
                self.properties[info.filename] = {
                    'compressed_size': info.compress_size,
                    'uncompressed_size': info.file_size,
                    'compression_method': info.compress_type,
                    'last_mod_date': info.date_time,
                    'crc': info.CRC
                }
                total_compressed += info.compress_size
                total_uncompressed += info.file_size
            self.properties['total_compressed_size'] = total_compressed
            self.properties['total_uncompressed_size'] = total_uncompressed

    def print_properties(self):
        if not self.properties:
            print("No properties loaded. Call read() first.")
            return
        for key, value in self.properties.items():
            if isinstance(value, dict):
                print(f"\nFile: {key}")
                for prop, val in value.items():
                    print(f"  {prop}: {val}")
            else:
                print(f"{key}: {value}")

    def write(self, output_filename, files_to_add):
        with zipfile.ZipFile(output_filename, 'w', zipfile.ZIP_DEFLATED) as zf:
            for file_path in files_to_add:
                zf.write(file_path, os.path.basename(file_path))
        print(f"Written new .PHZ to {output_filename}")

# Example usage:
# handler = PHZHandler('example.phz')
# handler.read()
# handler.print_properties()
# handler.write('new.phz', ['scene.phn', 'thumbnail.png', 'checksums.txt'])
  1. Java class for .PHZ:
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.util.Enumeration;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class PHZHandler {
    private String filename;
    private Map<String, Object> properties = new HashMap<>();

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

    public void read() throws IOException {
        try (ZipFile zf = new ZipFile(filename)) {
            properties.put("number_of_files", zf.size());
            long totalCompressed = 0;
            long totalUncompressed = 0;
            Enumeration<? extends ZipEntry> entries = zf.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                Map<String, Object> fileProps = new HashMap<>();
                fileProps.put("compressed_size", entry.getCompressedSize());
                fileProps.put("uncompressed_size", entry.getSize());
                fileProps.put("compression_method", entry.getMethod());
                fileProps.put("last_mod_date", entry.getTime());
                fileProps.put("crc", entry.getCrc());
                properties.put(entry.getName(), fileProps);
                totalCompressed += entry.getCompressedSize();
                totalUncompressed += entry.getSize();
            }
            properties.put("total_compressed_size", totalCompressed);
            properties.put("total_uncompressed_size", totalUncompressed);
        }
    }

    public void printProperties() {
        if (properties.isEmpty()) {
            System.out.println("No properties loaded. Call read() first.");
            return;
        }
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            if (entry.getValue() instanceof Map) {
                System.out.println("\nFile: " + entry.getKey());
                @SuppressWarnings("unchecked")
                Map<String, Object> fileProps = (Map<String, Object>) entry.getValue();
                for (Map.Entry<String, Object> prop : fileProps.entrySet()) {
                    System.out.println("  " + prop.getKey() + ": " + prop.getValue());
                }
            } else {
                System.out.println(entry.getKey() + ": " + entry.getValue());
            }
        }
    }

    public void write(String outputFilename, String[] filesToAdd) throws IOException {
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputFilename))) {
            for (String filePath : filesToAdd) {
                File file = new File(filePath);
                try (FileInputStream fis = new FileInputStream(file)) {
                    ZipEntry ze = new ZipEntry(file.getName());
                    zos.putNextEntry(ze);
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = fis.read(buffer)) > 0) {
                        zos.write(buffer, 0, len);
                    }
                    zos.closeEntry();
                }
            }
        }
        System.out.println("Written new .PHZ to " + outputFilename);
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     PHZHandler handler = new PHZHandler("example.phz");
    //     handler.read();
    //     handler.printProperties();
    //     handler.write("new.phz", new String[]{"scene.phn", "thumbnail.png", "checksums.txt"});
    // }
}
  1. JavaScript class for .PHZ:
const JSZip = require('jszip'); // Use jszip library
const fs = require('fs'); // For node.js

class PHZHandler {
    constructor(filename) {
        this.filename = filename;
        this.properties = {};
    }

    async read() {
        const data = fs.readFileSync(this.filename);
        const zip = await JSZip.loadAsync(data);
        this.properties.numberOfFiles = Object.keys(zip.files).length;
        let totalCompressed = 0;
        let totalUncompressed = 0;
        for (const filename in zip.files) {
            const zipFile = zip.files[filename];
            this.properties[filename] = {
                compressedSize: zipFile._data.compressedSize,
                uncompressedSize: zipFile._data.uncompressedSize,
                compressionMethod: zipFile.compression.method,
                lastModDate: zipFile.date,
            };
            totalCompressed += zipFile._data.compressedSize;
            totalUncompressed += zipFile._data.uncompressedSize;
        }
        this.properties.totalCompressedSize = totalCompressed;
        this.properties.totalUncompressedSize = totalUncompressed;
    }

    printProperties() {
        if (Object.keys(this.properties).length === 0) {
            console.log('No properties loaded. Call read() first.');
            return;
        }
        for (const key in this.properties) {
            if (typeof this.properties[key] === 'object') {
                console.log(`\nFile: ${key}`);
                for (const prop in this.properties[key]) {
                    console.log(`  ${prop}: ${this.properties[key][prop]}`);
                }
            } else {
                console.log(`${key}: ${this.properties[key]}`);
            }
        }
    }

    async write(outputFilename, filesToAdd) {
        const zip = new JSZip();
        for (const filePath of filesToAdd) {
            const data = fs.readFileSync(filePath);
            zip.file(filePath, data);
        }
        const content = await zip.generateAsync({type: 'nodebuffer'});
        fs.writeFileSync(outputFilename, content);
        console.log(`Written new .PHZ to ${outputFilename}`);
    }
}

// Example usage:
// const handler = new PHZHandler('example.phz');
// await handler.read();
// handler.printProperties();
// await handler.write('new.phz', ['scene.phn', 'thumbnail.png', 'checksums.txt']);
  1. C class for .PHZ:

Note: C does not have built-in ZIP support, so this uses a simple approach assuming minizip or similar library is available. For simplicity, this is a basic structure without full ZIP parsing code (as it would be lengthy).

#include <stdio.h>
#include <zip.h> // Assume libzip is installed

struct PHZHandler {
    const char* filename;
    // Properties would be stored in a struct or map, but for simplicity use printf directly
};

void phz_read(const char* filename) {
    int err = 0;
    zip_t *z = zip_open(filename, 0, &err);
    if (z == NULL) {
        printf("Error opening %s\n", filename);
        return;
    }

    zip_int64_t num_entries = zip_get_num_entries(z, 0);
    printf("Number of files: %lld\n", num_entries);

    long total_compressed = 0;
    long total_uncompressed = 0;

    for (zip_int64_t i = 0; i < num_entries; i++) {
        struct zip_stat st;
        zip_stat_index(z, i, 0, &st);
        printf("\nFile: %s\n", st.name);
        printf("  Compressed size: %llu\n", st.comp_size);
        printf("  Uncompressed size: %llu\n", st.size);
        printf("  Compression method: %u\n", st.comp_method);
        printf("  Last mod time: %u\n", st.mtime);
        printf("  CRC: %u\n", st.crc);
        total_compressed += st.comp_size;
        total_uncompressed += st.size;
    }

    printf("\nTotal compressed size: %ld\n", total_compressed);
    printf("Total uncompressed size: %ld\n", total_uncompressed);

    zip_close(z);
}

void phz_write(const char* output_filename, const char** files_to_add, int num_files) {
    int err = 0;
    zip_t *z = zip_open(output_filename, ZIP_CREATE | ZIP_TRUNCATE, &err);
    if (z == NULL) {
        printf("Error creating %s\n", output_filename);
        return;
    }

    for (int i = 0; i < num_files; i++) {
        zip_source_t *src = zip_source_file(z, files_to_add[i], 0, 0);
        if (src == NULL || zip_file_add(z, files_to_add[i], src, ZIP_FL_OVERWRITE) < 0) {
            zip_source_free(src);
            printf("Error adding file %s\n", files_to_add[i]);
        }
    }

    zip_close(z);
    printf("Written new .PHZ to %s\n", output_filename);
}

// Example usage:
// int main() {
//     phz_read("example.phz");
//     const char* files[] = {"scene.phn", "thumbnail.png", "checksums.txt"};
//     phz_write("new.phz", files, 3);
//     return 0;
// }