Vaguely PAK like file format supporting compression and read/write operation (as zipfiles are not that well suited to read/write).
The format will assume no fragmentation of files, rather a file is to be moved if it expands beyond the space available to it.

Header
{
FOURCC magic;	//'ZPAK'
u32 ents;		//size of directory
u64 offs;		//offset of directory
}

DirEntry
{
char name[32];	//file name, 0 padded
u32 chain;		//next dir entry
u32 date_time;	//date modified
u16 flags;		//file flags (1&=fragmented, 2&=dir)
u16 method;		//method number, 0=store, 8=deflate, 9=deflate(ext)
u32 crc32;		//CRC32 of data (or first entry if dir)
u32 usize;		//uncompressed size of data
u32 csize;		//compressed size of data
u64 offset;		//offset to start of data
}

The name will contain the filename and is limited to 32 characters.
A name beginning with 0 is not allowed, it will signify a free entry. 

For longer names, consider a name mangling scheme. For names exceeding 32 chars, these will be rewritten to consist of:
	The first some-odd chars of the name;
	A hash value for the full name;
	The file extension (if present).

The hash value will be preceded by '~', and consist of 4 base-36 digits (high-low order), with the hash function:
	h=0;
	while(*s)
	{
		i=*s++;
		h=(h>>20)^(h<<3)^(((i>='A')&&(i<='Z'))?(i-'A'+'a'):i);
	}

Chain will refer to the next entry in the current directory. A chain value of 0 will indicate that this is the last entry in the directory.

Note that entry 0 is special, and will serve primarily to link to the first root entry.


Vs. the date/time being in dos format:
	date, low 5 bits=day, next 4 bits=month, high 7=year rel 1980;
	time, low 5=seconds/2, next 6 bits=minute, high 5=hour
The date_time field multiplies and adds the values:
	60s, 60m, 24h, 31d, 12m, 133.63y (fails in 2113 vs 2107).
	so:
	s=date_time%60;
	m=(date_time/60)%60;
	h=(date_time/3600)%24;
	...

Considered:
1&=the file is fragmented. Offset/csize refers to a table of spans, each giving the offset and size of each fragment. This could be useful for larger files, where possibly relocating on each write may not be all that efficient. Default chunking will be 64kB.

2&=directory. The CRC field will refer to the first directory entry (or 0 if the directory is empty). Usize, csize, and offset are reserved. Method will be 0.

4&=(file) micro file, method will store the length, and the crc32, usize, csize, and offset fields will store the file's data (up to 20 bytes).

4&=(dir, optional) directory is indexed. In this case, contents are present as in a normal file, except that they may not be compressed, and refer to a directory index tree.


Method 9 will be deflate with a 64kB window.
The CRC algo will be the same algo used in ZIP and PNG for example.


So, very little extra is stored in the file itself.

The thought for read/write access will be based on building a list of spans. All the empty space will be given to free spans, and all used space given to used spans. Idea here: First, all used spans are accumulated and sorted, and free spans are inferred from any breaks between the spans.

Files will be read in and decompressed on open, and, if modified, recompressed on close. If a file does not fit within a given span, it will be relocated to a span that fits, and, failing that, to the end of the image.

Fragmented Files:
Fragmented files will have the contents refer to an array of spans.

FragmentSpan {
u32 _resv0;		//reserved, 0
u32 _resv1;		//reserved, 0
u16 flags;		//fragment flags
u16 method;		//method number, 0=store, 8=deflate, 9=deflate(ext)
u32 crc32;		//CRC32 of data
u32 usize;		//uncompressed size of data
u32 csize;		//compressed size of data
u64 offset;		//offset to start of data
}

Note that all spans apart from the last span are required to have the same uncompressed size, and this size is required to be a power of 2.
In effect, the uncompressed size of the first span will be used to know the fragment size.


Special Files:

All files with names beginning with '$' are special and will not be visible (or accessible) as normal directory contents.

Most special files will exist standalone, and behave like that of normal files (only one per directory with a given name, ...).


Forks:

Names beginning with '$:' are "forks". Forks will directly follow the files to which they apply and need not have unique names in a directory. Forks may be used for optionally supplying additional metadata about files, but should not store any info vital to the correct functioning of the file, as forks may be readily discarded in some cases (eg: when extracting to the host filesystem, ...).

Forks will usually be appended onto a filename using the ':' symbol.

'name' fork, may be used for holding names which overflow the 32 char limit. These names may not necissarily serve much use when retrieving files, but may allow preserving full names for extraction purposes.


Compression:

Could either default to some algo, or try possible algos to determine which is best. Could detect write frequency, and for sufficiently oftenly modified data could default to store.


Directory Index Trees (possible feature):

Directory Index Trees would be used for optimizing directory lookups. Several designs are possible here, eg, AVL trees or B-Trees. An AVL tree would be easier to implement, if albeit not as fast.

In this case, Left and right values < 0 are leaves, and map to directory entries. Values > 0 map to nodes, and 0 is a reserved value.
The middle value is different in that it always points directly to a directory entry (the entry in question is used as a pivot, and does not appear in any of the sub-trees).

DINode {
u32 left;		//values less than pivot
u32 right;		//values greater than pivot
u32 mid;		//pivot
byte ldepth;	//depth of left side
byte rdepth;	//depth of right side
byte _resv[2];
}

Node 0 is special, and is not a node, but rather a header.

DIHead {
u32 root;		//root of tree
u32 free;		//first free entry
u32 _resv0;
byte depth;		//total depth of tree
byte _resv1[3];	//reserved
}
