PureFS
Filesystems |
---|
Virtual Filesystems |
Disk Filesystems |
CD/DVD Filesystems |
Network Filesystems |
Flash Filesystems |
PureFS is a simple disk file system designed to quickly read information and data from files and directories.
Due to its structure, it is possible to use a second file system on the disk, you just need to create a 3rd or 4th entry for PureFS in the MBR partition table(SystemID = 0x15).
PureFS 1.1.0
There are only 4 structures:
- PureFsHeader
- PureFsPartitionHeader
- PureFsFile
- PureFsData
Note: all values must be written in Little-Endian format!
PureFsHeader
Contains information about file system.
Offset | Size | Name | Value/Description |
---|---|---|---|
0x00 | 0x08 | Signature | `->PureFS` |
0x08 | 0x04 | Version | Version number as a hex value (for ex.: 1.1.0 = 0x010100) |
0x0C | 0x08 | FirstPartition | Offset to first partition (in sectors) |
0x14 | 0x08 | NumPartitions | Number of PureFS partitions |
0x1C | 0x08 | Checksum | With a byte-by-byte sum of all fields above it should give 0 |
0x1D | 0x1E3 | Reserved | Reserved for future use |
PureFsPartitionHeader
Contains information about partition.
Offset | Size | Name | Value/Description |
---|---|---|---|
0x00 | 0x40 | Name | Name of the partition |
0x40 | 0x08 | Attributes | Partition attributes |
0x48 | 0x08 | NumBlocks | Maximum number of blocks(sectors) in partition |
0x50 | 0x08 | NumFiles | Current number of files in partition |
0x58 | 0x08 | FirstFile | Offset to first file block (in sectors) |
0x60 | 0x08 | LastFile | Offset to last file block (in sectors) |
0x68 | 0x08 | FirstData | Offset to first data block (in sectors) |
0x70 | 0x08 | LastData | Offset to last data block (in sectors) |
0x78 | 0x08 | NextPartition | Offset to next partition (in sectors) |
0x80 | 0x180 | Reserved | Reserved for future use |
Attributes:
- SYSTEM(0x01) - partition required for the file system to function
- BOOTABLE(0x02) - the partition contains a program that can be loaded by the bootloader
- HIDDEN(0x04)
- READONLY(0x08)
- WRITEONLY(0x10)
NextPartition: if this is the last partition, this field is 0
PureFsFile
Contains information about file.
Offset | Size | Name | Value/Description |
---|---|---|---|
0x00 | 0x100 | Name | File name (ASCII) |
0x100 | 0x08 | Attributes | File attributes |
0x108 | 0x08 | NumBlocks | Number of data blocks(`PureFsData`) of the file |
0x110 | 0x08 | OriginalSize | Original file data size |
0x118 | 0x08 | FirstBlock | Offset to first data block(`PureFsData`) (in sectors) |
0x120 | 0x04 | PathHash | Murmur3 32-bit hash of the file path on the partition |
0x124 | 0x04 | ParentHash | Murmur3 32-bit hash of the path to the file's parent directory |
0x128 | 0xD8 | Reserved | Reserved for future use |
Attributes:
- SYSTEM(0x01) - file required for the file system to function
- EXECUTABLE(0x02) - it is possible to run the file as a program
- HIDDEN(0x04)
- READONLY(0x08)
- WRITEONLY(0x10)
- DIRECTORY(0x20) - marks that it is a directory and not a file
OriginalSize: if the file is empty or it is a directory, it is equal to 0
FirstBlock: if the file is empty or it is a directory, it is equal to 0
PureFsData
Contains one of the file's data parts.
Offset | Size | Name | Value/Description |
---|---|---|---|
0x00 | 0x1F8 | Data | One of the file data parts |
0x1F8 | 0x08 | NextData | Offset to next data block(`PureFsData`) (in sectors) |
NextData: if this is the last block of data, this field is 0
Implementation of some functions
purefs.h:
#pragma once
#ifndef PUREFS_H
#define PUREFS_H
#include <stdint.h>
#include <string.h>
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
#pragma pack(push, 1)
#define PUREFS_SIGNATURE "->PureFS"
#define PUREFS_V1_1_0 0x010100
#define PUREFS_SYSID 0x15
#define PUREFS_MURMUR3_SEED 0x4E53544B
typedef struct _PureFsHeader {
i8 Signature[8];
u32 Version;
u64 FirstPartition;
u64 NumPartitions;
u8 Checksum;
u8 Reserved[0x1E3];
} PureFsHeader;
#define PUREFS_PARTITION_ATR_SYSTEM 0x01
#define PUREFS_PARTITION_ATR_BOOTABLE 0x02
#define PUREFS_PARTITION_ATR_HIDDEN 0x04
#define PUREFS_PARTITION_ATR_READONLY 0x08
#define PUREFS_PARTITION_ATR_WRITEONLY 0x10
typedef struct _PureFsPartitionHeader {
i8 Name[64];
u64 Attributes;
u64 NumBlocks;
u64 NumFiles;
u64 FirstFile;
u64 LastFile;
u64 FirstData;
u64 LastData;
u64 NextPartition;
u8 Reserved[0x180];
} PureFsPartitionHeader;
#define PUREFS_FILE_ATR_SYSTEM 0x01
#define PUREFS_FILE_ATR_EXECUTABLE 0x02
#define PUREFS_FILE_ATR_HIDDEN 0x04
#define PUREFS_FILE_ATR_READONLY 0x08
#define PUREFS_FILE_ATR_WRITEONLY 0x10
#define PUREFS_FILE_ATR_DIRECTORY 0x20
typedef struct _PureFsFile {
i8 Name[256];
u64 Attributes;
u64 NumBlocks;
u64 OriginalSize;
u64 FirstBlock;
u32 PathHash;
u32 ParentHash;
u8 Reserved[0xD8];
} PureFsFile;
typedef struct _PureFsData {
u8 Data[504];
u64 NextData;
} PureFsData;
#pragma pack(pop)
extern size_t ReadSector(void *disk, u64 lba, u8 *buf);
extern u32 murmur3_32(u8 *key, size_t len, u32 seed);
size_t PureFsCheckHeader(PureFsHeader *h);
size_t PureFsReadHeader(void *disk, PureFsHeader *h);
size_t PureFsReadPartitionHeader(void *disk, const char *name, PureFsHeader *fsh, PureFsPartitionHeader *ph);
size_t PureFsReadFile(void *disk, const char *path, PureFsHeader *h, PureFsFile *buf1, u8 *buf2);
#endif
purefs.c:
#include <purefs.h>
// Returns: 0 - invalid PureFS header, 1 - valid
size_t PureFsCheckHeader(PureFsHeader *h) {
u8 csum;
u8 i;
if (!strcmp(h->Signature, PUREFS_SIGNATURE) || h->Version != PUREFS_V1_1_0) return 0;
csum = 0;
for (i = 0; i < 0x1D; ++i) csum == ((u8*)h)[i];
return !csum;
}
// Returns LBA of the sector with PureFsHeader or 0
size_t PureFsReadHeader(void *disk, PureFsHeader *h) {
u8 MBR[512];
size_t off;
size_t i;
u64 lba;
if (!h || !ReadSector(disk, 0, &MBR[0])) return 0;
// Finding a PureFS entry in the MBR partition table
lba = 0;
off = 440 + 4 + 2;
for (i = 0; i < 4; ++i) {
// We check that the partition is active and its ID is PureFS(0x15)
if (*((u8*)off) & 0x80 && *((u8*)(off + 4)) == PUREFS_SYSID) {
lba = (u64)*((u32*)(off + 8));
if (lba) break;
}
}
if (!lba || !ReadSector(disk, lba, h) || !PureFsCheckHeader(h)) return 0;
return lba;
}
// Returns LBA of the sector with PureFsPartitionHeader or 0
size_t PureFsReadPartitionHeader(void *disk, const char *name, PureFsHeader *fsh, PureFsPartitionHeader *ph) {
u64 lba;
if (!fsh->FirstPartition || !fsh->NumPartitions) return 0;
lba = (u64)fsh->FirstPartition;
do {
if (!ReadSector(disk, lba, ph)) continue;
if (!strcmp(ph->Name, name)) return lba;
lba = ph->NextPartition;
} while (ph->NextPartition);
return 0;
}
// Returns LBA of the sector with PureFsFile or 0
size_t PureFsReadFile(void *disk, const char *path, PureFsHeader *h, PureFsFile *buf1, u8 *buf2) {
PureFsPartitionHeader ph;
char *separator;
char tmp[512];
PureFsFile f;
size_t found;
size_t len;
u32 hash;
u64 lba;
u64 i;
u64 j;
if (!h || !h->FirstPartition || !h->NumPartitions) return 0;
// Get file path hash
hash = murmur3_32(path, strlen(path), PUREFS_MURMUR3_SEED);
while (*path == '/') ++path;
separator = strchr(path, '/');
if (!separator) return 0;
len = (size_t)separator - (size_t)path;
memcpy(tmp, path, len);
tmp[len] = 0;
// Looking for a partition by name
found = 0;
lba = (u64)h->FirstPartition;
do {
if (!ReadSector(disk, lba, &ph)) continue;
if (!strcmp(ph.Name, tmp)) {
found = 1;
break;
}
lba = ph.NextPartition;
} while (ph.NextPartition);
if (!found || !ph.NumBlocks || !ph.NumFiles || !ph.FirstFile) return 0;
// Looking for file
found = 0;
lba += ph.FirstFile;
for (i = 0; i < ph.NumFiles; ++i) {
if (!ReadSector(disk, lba, &f)) continue;
if (f.PathHash == hash) {
found = 1;
break;
}
++lba;
}
if (!found) return 0;
// Copy PureFsFile and read data as needed
if (buf1) memcpy(buf1, &f, sizeof(PureFsFile));
if (f.FirstBlock && buf2) {
len = f.OriginalSize;
j = lba + f.FirstBlock;
for (i = 0; i < f.NumBlocks - 1; ++i) {
if (!ReadSector(disk, j, tmp) || !((PureFsData*)tmp)->NextData) return 0;
memcpy(buf2, tmp, 504);
buf2 = (u8*)((size_t)buf2 + 504);
len -= 504;
++j;
}
if (!ReadSector(disk, j, tmp)) return 0;
memcpy(buf2, tmp, len);
}
return lba;
}