diff --git a/CMakeLists.txt b/CMakeLists.txt
index 40fc5d9..6e65ecf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -117,7 +117,11 @@ add_library(aaruformat SHARED include/aaruformat/consts.h include/aaruformat/enu
src/blocks/dump.c
src/blocks/checksum.c
src/index/index_v3.c
- src/ddt/ddt_v2.c)
+ src/ddt/ddt_v2.c
+ include/aaruformat/structs/options.h
+ src/options.c
+ src/create.c
+ src/time.c)
include_directories(include include/aaruformat)
diff --git a/include/aaruformat/context.h b/include/aaruformat/context.h
index 61ea7d6..70fd81e 100644
--- a/include/aaruformat/context.h
+++ b/include/aaruformat/context.h
@@ -115,7 +115,7 @@ typedef struct aaruformatContext
uint64_t cachedDdtOffset;
uint16_t *cachedSecondaryDdtSmall;
uint32_t *cachedSecondaryDdtBig;
-
+ bool isWriting;
} aaruformatContext;
typedef struct DumpHardwareEntriesWithData
diff --git a/include/aaruformat/decls.h b/include/aaruformat/decls.h
index 7cbfc3a..c49a29e 100644
--- a/include/aaruformat/decls.h
+++ b/include/aaruformat/decls.h
@@ -62,6 +62,11 @@ AARU_EXPORT int AARU_CALL aaruf_identify_stream(FILE *imageStream);
AARU_EXPORT void *AARU_CALL aaruf_open(const char *filepath);
+AARU_EXPORT void *AARU_CALL aaruf_create(const char *filepath, uint32_t mediaType, uint32_t sectorSize,
+ uint64_t userSectors, uint64_t negativeSectors, uint64_t overflowSectors,
+ const char *options, const uint8_t *applicationName,
+ uint8_t applicationMajorVersion, uint8_t applicationMinorVersion);
+
AARU_EXPORT int AARU_CALL aaruf_close(void *context);
AARU_EXPORT int32_t AARU_CALL aaruf_read_media_tag(void *context, uint8_t *data, int32_t tag, uint32_t *length);
diff --git a/include/aaruformat/errors.h b/include/aaruformat/errors.h
index 670d72b..faca2ff 100644
--- a/include/aaruformat/errors.h
+++ b/include/aaruformat/errors.h
@@ -37,6 +37,9 @@
#define AARUF_ERROR_SECTOR_TAG_NOT_PRESENT -16
#define AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK -17
#define AARUF_ERROR_INVALID_BLOCK_CRC -18
+#define AARUF_ERROR_CANNOT_CREATE_FILE -19
+#define AARUF_ERROR_INVALID_APP_NAME_LENGTH -20
+#define AARUF_ERROR_CANNOT_WRITE_HEADER -21
#define AARUF_STATUS_OK 0
#define AARUF_STATUS_SECTOR_NOT_DUMPED 1
diff --git a/include/aaruformat/structs.h b/include/aaruformat/structs.h
index f0b193f..58e0445 100644
--- a/include/aaruformat/structs.h
+++ b/include/aaruformat/structs.h
@@ -37,6 +37,7 @@
#include "structs/index.h"
#include "structs/metadata.h"
#include "structs/optical.h"
+#include "structs/options.h"
#endif // LIBAARUFORMAT_STRUCTS_H
diff --git a/include/aaruformat/structs/options.h b/include/aaruformat/structs/options.h
new file mode 100644
index 0000000..583df16
--- /dev/null
+++ b/include/aaruformat/structs/options.h
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the Aaru Data Preservation Suite.
+ * Copyright (c) 2019-2025 Natalia Portillo.
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#ifndef LIBAARUFORMAT_OPTIONS_H
+#define LIBAARUFORMAT_OPTIONS_H
+
+typedef struct
+{
+ bool compress;
+ bool deduplicate;
+ uint32_t dictionary;
+ uint8_t table_shift;
+ uint8_t data_shift;
+ uint8_t block_alignment;
+ bool md5;
+ bool sha1;
+ bool sha256;
+ bool blake3;
+ bool spamsum;
+} aaru_options;
+
+#endif // LIBAARUFORMAT_OPTIONS_H
diff --git a/include/internal.h b/include/internal.h
index 43a7c8c..5121fec 100644
--- a/include/internal.h
+++ b/include/internal.h
@@ -45,5 +45,7 @@ int32_t decode_ddt_single_level_v2(aaruformatContext *ctx, uint64_t sectorAddr
uint64_t *blockOffset, uint8_t *sectorStatus);
int32_t decode_ddt_multi_level_v2(aaruformatContext *ctx, uint64_t sectorAddress, uint64_t *offset,
uint64_t *blockOffset, uint8_t *sectorStatus);
+aaru_options parse_options(const char *options);
+uint64_t get_filetime_uint64();
#endif // LIBAARUFORMAT_INTERNAL_H
diff --git a/src/close.c b/src/close.c
index a5e1a7e..a792dde 100644
--- a/src/close.c
+++ b/src/close.c
@@ -47,6 +47,19 @@ int aaruf_close(void *context)
return -1;
}
+ if(ctx->isWriting)
+ {
+ // Write the header at the beginning of the file
+ fseek(ctx->imageStream, 0, SEEK_SET);
+ if(fwrite(&ctx->header, sizeof(AaruHeaderV2), 1, ctx->imageStream) != 1)
+ {
+ fclose(ctx->imageStream);
+ ctx->imageStream = NULL;
+ errno = AARUF_ERROR_CANNOT_WRITE_HEADER;
+ return -1;
+ }
+ }
+
// This may do nothing if imageStream is NULL, but as the behaviour is undefined, better sure than sorry
if(ctx->imageStream != NULL)
{
diff --git a/src/create.c b/src/create.c
new file mode 100644
index 0000000..fd509c2
--- /dev/null
+++ b/src/create.c
@@ -0,0 +1,145 @@
+/*
+ * This file is part of the Aaru Data Preservation Suite.
+ * Copyright (c) 2019-2025 Natalia Portillo.
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "aaruformat.h"
+#include "internal.h"
+
+void *aaruf_create(const char *filepath, uint32_t mediaType, uint32_t sectorSize, uint64_t userSectors,
+ uint64_t negativeSectors, uint64_t overflowSectors, const char *options,
+ const uint8_t *applicationName, uint8_t applicationNameLength, uint8_t applicationMajorVersion,
+ uint8_t applicationMinorVersion);
+{
+ // Parse the options
+ aaru_options parsedOptions = parse_options(options);
+
+ // Allocate context
+ aaruformatContext *ctx = (aaruformatContext *)malloc(sizeof(aaruformatContext));
+ if(ctx == NULL)
+ {
+ errno = AARUF_ERROR_NOT_ENOUGH_MEMORY;
+ return NULL;
+ }
+
+ memset(ctx, 0, sizeof(aaruformatContext));
+
+ // Create the image file
+ ctx->imageStream = fopen(filepath, "wb+");
+ if(ctx->imageStream == NULL)
+ {
+ free(ctx);
+ errno = AARUF_ERROR_CANNOT_CREATE_FILE;
+ return NULL;
+ }
+
+ if(applicationNameLength > AARU_HEADER_APP_NAME_LEN)
+ {
+ free(ctx);
+ errno = AARUF_ERROR_INVALID_APP_NAME_LENGTH;
+ return NULL;
+ }
+
+ // Initialize header
+ ctx->header.identifier = AARU_MAGIC;
+ memcpy(ctx->header.application, applicationName, applicationNameLength);
+ ctx->header.imageMajorVersion = AARUF_VERSION_V2;
+ ctx->header.imageMinorVersion = 0;
+ ctx->header.applicationMajorVersion = applicationMajorVersion;
+ ctx->header.applicationMinorVersion = applicationMinorVersion;
+ ctx->header.mediaType = mediaType;
+ ctx->header.indexOffset = 0;
+ ctx->header.creationTime = get_filetime_uint64();
+ ctx->header.lastWrittenTime = get_filetime_uint64();
+
+ ctx->readableSectorTags = (bool *)malloc(sizeof(bool) * MaxSectorTag);
+
+ if(ctx->readableSectorTags == NULL)
+ {
+ free(ctx);
+ errno = AARUF_ERROR_NOT_ENOUGH_MEMORY;
+
+ return NULL;
+ }
+
+ memset(ctx->readableSectorTags, 0, sizeof(bool) * MaxSectorTag);
+
+ // Initilize image info
+ ctx->imageInfo.Application = ctx->header.application;
+ ctx->imageInfo.ApplicationVersion = (uint8_t *)malloc(32);
+ if(ctx->imageInfo.ApplicationVersion != NULL)
+ {
+ memset(ctx->imageInfo.ApplicationVersion, 0, 32);
+ sprintf((char *)ctx->imageInfo.ApplicationVersion, "%d.%d", ctx->header.applicationMajorVersion,
+ ctx->header.applicationMinorVersion);
+ }
+ ctx->imageInfo.Version = (uint8_t *)malloc(32);
+ if(ctx->imageInfo.Version != NULL)
+ {
+ memset(ctx->imageInfo.Version, 0, 32);
+ sprintf((char *)ctx->imageInfo.Version, "%d.%d", ctx->header.imageMajorVersion, ctx->header.imageMinorVersion);
+ }
+ ctx->imageInfo.MediaType = ctx->header.mediaType;
+ ctx->imageInfo.ImageSize = 0;
+ ctx->imageInfo.CreationTime = ctx->header.creationTime;
+ ctx->imageInfo.LastModificationTime = ctx->header.lastWrittenTime;
+ ctx->imageInfo.XmlMediaType = aaruf_get_xml_mediatype(ctx->header.mediaType);
+ ctx->imageInfo.SectorSize = sectorSize;
+
+ // Initialize caches
+ ctx->blockHeaderCache.cache = NULL;
+ ctx->blockHeaderCache.max_items = MAX_CACHE_SIZE / (ctx->imageInfo.SectorSize * (1 << ctx->shift));
+ ctx->blockCache.cache = NULL;
+ ctx->blockCache.max_items = ctx->blockHeaderCache.max_items;
+
+ // TODO: Cache tracks and sessions?
+
+ // Initialize ECC for Compact Disc
+ ctx->eccCdContext = (CdEccContext *)aaruf_ecc_cd_init();
+
+ ctx->magic = AARU_MAGIC;
+ ctx->libraryMajorVersion = LIBAARUFORMAT_MAJOR_VERSION;
+ ctx->libraryMinorVersion = LIBAARUFORMAT_MINOR_VERSION;
+
+ // Initialize DDT2
+ ctx->inMemoryDdt = true;
+ ctx->userDataDdtHeader.identifier = DeDuplicationTable2;
+ ctx->userDataDdtHeader.type = UserData;
+ ctx->userDataDdtHeader.compression = None;
+ ctx->userDataDdtHeader.levels = 2;
+ ctx->userDataDdtHeader.tableLevel = 0;
+ ctx->userDataDdtHeader.previousLevelOffset = 0;
+ ctx->userDataDdtHeader.negative = negativeSectors;
+ ctx->userDataDdtHeader.blocks = userSectors + overflowSectors + negativeSectors;
+ ctx->userDataDdtHeader.overflow = overflowSectors;
+ ctx->userDataDdtHeader.start = 0;
+ ctx->userDataDdtHeader.blockAlignmentShift = parsedOptions.block_alignment;
+ ctx->userDataDdtHeader.dataShift = parsedOptions.data_shift;
+ ctx->userDataDdtHeader.tableShift = parsedOptions.table_shift;
+ ctx->userDataDdtHeader.sizeType = 1;
+ ctx->userDataDdtHeader.entries = ctx->userDataDdtHeader.blocks / (1 << ctx->userDataDdtHeader.tableShift);
+
+ if(ctx->userDataDdtHeader.blocks % (1 << ctx->userDataDdtHeader.tableShift) != 0) ctx->userDataDdtHeader.entries++;
+
+ // Is writing
+ ctx->isWriting = true;
+}
diff --git a/src/options.c b/src/options.c
new file mode 100644
index 0000000..9d00e11
--- /dev/null
+++ b/src/options.c
@@ -0,0 +1,96 @@
+/*
+ * This file is part of the Aaru Data Preservation Suite.
+ * Copyright (c) 2019-2025 Natalia Portillo.
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+#include
+#include
+#include
+#include
+#include
+
+#include "internal.h"
+#include "structs/options.h"
+
+aaru_options parse_options(const char *options)
+{
+ aaru_options parsed = {.compress = true,
+ .deduplicate = true,
+ .dictionary = 33554432,
+ .table_shift = 9,
+ .data_shift = 12,
+ .block_alignment = 9,
+ .md5 = false,
+ .sha1 = false,
+ .sha256 = false,
+ .blake3 = false,
+ .spamsum = false};
+
+ char buffer[1024];
+ strncpy(buffer, options, sizeof(buffer));
+ buffer[sizeof(buffer) - 1] = '\0';
+
+ const char *token = strtok(buffer, ";");
+ while(token != NULL)
+ {
+ char *equal = strchr(token, '=');
+ if(equal)
+ {
+ *equal = '\0';
+ const char *key = token;
+ const char *value = equal + 1;
+
+ bool bval = strncmp(value, "true", 4) == 0;
+
+ if(strncmp(key, "compress", 8) == 0)
+ parsed.compress = bval;
+ else if(strncmp(key, "deduplicate", 11) == 0)
+ parsed.deduplicate = bval;
+ else if(strncmp(key, "dictionary", 10) == 0)
+ {
+ parsed.dictionary = (uint32_t)strtoul(value, NULL, 10);
+ if(parsed.dictionary == 0) parsed.dictionary = 33554432;
+ }
+ else if(strncmp(key, "table_shift", 11) == 0)
+ {
+ parsed.table_shift = (uint8_t)atoi(value);
+ if(parsed.table_shift == 0) parsed.table_shift = 9;
+ }
+ else if(strncmp(key, "data_shift", 10) == 0)
+ {
+ parsed.data_shift = (uint8_t)atoi(value);
+ if(parsed.data_shift == 0) parsed.data_shift = 12;
+ }
+ else if(strncmp(key, "block_alignment", 15) == 0)
+ {
+ parsed.block_alignment = (uint8_t)atoi(value);
+ if(parsed.block_alignment == 0) parsed.block_alignment = 9;
+ }
+ else if(strncmp(key, "md5", 3) == 0)
+ parsed.md5 = bval;
+ else if(strncmp(key, "sha1", 4) == 0)
+ parsed.sha1 = bval;
+ else if(strncmp(key, "sha256", 6) == 0)
+ parsed.sha256 = bval;
+ else if(strncmp(key, "blake3", 6) == 0)
+ parsed.blake3 = bval;
+ else if(strncmp(key, "spamsum", 7) == 0)
+ parsed.spamsum = bval;
+ }
+ token = strtok(NULL, ";");
+ }
+
+ return parsed;
+}
\ No newline at end of file
diff --git a/src/time.c b/src/time.c
new file mode 100644
index 0000000..294f396
--- /dev/null
+++ b/src/time.c
@@ -0,0 +1,46 @@
+/*
+ * This file is part of the Aaru Data Preservation Suite.
+ * Copyright (c) 2019-2025 Natalia Portillo.
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see .
+ */
+
+#include
+#include
+
+#ifdef _WIN32
+#include
+
+uint64_t get_filetime_uint64()
+{
+ FILETIME ft;
+ SYSTEMTIME st;
+ GetSystemTime(&st); // UTC time
+ SystemTimeToFileTime(&st, &ft);
+ return ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
+}
+
+#else
+#include
+
+uint64_t get_filetime_uint64()
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL); // seconds + microseconds since 1970
+
+ const uint64_t EPOCH_DIFF = 11644473600ULL; // seconds between 1601 and 1970
+ uint64_t ft = (tv.tv_sec + EPOCH_DIFF) * 10000000ULL + tv.tv_usec * 10;
+ return ft;
+}
+#endif