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.
- 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.
- Two direct download links for files of format .PHZ:
- https://leon1111.itch.io/algodoo-leon/download/1 (CAR ICE.phz - download from the itch.io page by clicking the corresponding button).
- https://leon1111.itch.io/algodoo-leon/download/2 (ORANGE BOY.phz - download from the itch.io page by clicking the corresponding button).
(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.)
- Ghost blog embedded HTML JavaScript for drag and drop .PHZ file to dump properties:
Drag and Drop .PHZ File
- 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'])
- 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"});
// }
}
- 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']);
- 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;
// }