Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ Makefile

api_test
ccos_disk_tool

build/
.cache/
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"clangd.arguments": [
"--compile-commands-dir=build"
]
}
22 changes: 15 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
cmake_minimum_required(VERSION 3.10)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

project(ccos_disk_tool)

set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${SOURCE_DIR})

option(CCOS_ENABLE_TESTS "Build and run tests (Criterion)" OFF)

add_library(ccos_api STATIC
${SOURCE_DIR}/string_utils.h
${SOURCE_DIR}/string_utils.c
${SOURCE_DIR}/common.h
${SOURCE_DIR}/common.c
${SOURCE_DIR}/ccos_disk.h
${SOURCE_DIR}/ccos_image.h
${SOURCE_DIR}/ccos_image.c
${SOURCE_DIR}/ccos_boot_data.h
${SOURCE_DIR}/ccos_format.h
${SOURCE_DIR}/ccos_format.c
${SOURCE_DIR}/ccos_private.h
${SOURCE_DIR}/ccos_private.c
${SOURCE_DIR}/ccos_structure.h
Expand All @@ -26,10 +36,8 @@ add_executable(ccos_disk_tool

TARGET_LINK_LIBRARIES(ccos_disk_tool ccos_api)

project(api_test)

add_executable(api_test
${SOURCE_DIR}/api_test.c
)

TARGET_LINK_LIBRARIES(api_test ccos_api)
if(CCOS_ENABLE_TESTS)
include(CTest)
enable_testing()
add_subdirectory(tests)
endif()
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,29 @@ OPTIONS:
-l, --in-place Write changes to the original image
```

## Build

To build the project, run the following commands:

```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
```

After building, the compiled binary will be located at `build/ccos_disk_tool`.

## Run Tests

To run the tests, use the following commands:

```bash
cmake -S . -B build -DCCOS_ENABLE_TESTS=ON
cmake --build build --parallel
ctest --test-dir build --output-on-failure
```

Tests depend on the [Criterion](https://github.com/Snaipe/Criterion) library, which must be installed before running the tests.

## Examples

### Working with bubble memory images or other non-standard images
Expand Down
308 changes: 308 additions & 0 deletions ccos_boot_data.h

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions ccos_context.h → ccos_disk.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
#define CCOS_CONTEXT_H

#include <stdint.h>
#include <stddef.h>

typedef struct {
uint16_t sector_size;
uint16_t superblock_id;
uint16_t bitmap_block_id;
} ccfs_context_t;

typedef const ccfs_context_t* ccfs_handle;
uint16_t superblock_fid;
uint16_t bitmap_fid;
size_t size;
uint8_t* data;
} ccos_disk_t;

#endif // CCOS_CONTEXT_H
251 changes: 251 additions & 0 deletions ccos_format.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
#include "ccos_format.h"
#include "ccos_boot_data.h"

#include "ccos_boot_data.h"
#include "common.h"
#include "ccos_disk.h"
#include "ccos_structure.h"
#include "ccos_private.h"

#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <time.h>

#define SECTOR(disk_ptr, id) ((void*)(disk_ptr)->data + (id) * (disk_ptr)->sector_size)

typedef struct {
uint16_t sector;
uint16_t count;
uint16_t tail_length;
uint16_t tail_offset;
} bitmask_info_t;

static uint8_t* new_empty_image(uint16_t sector_size, size_t disk_size) {
uint8_t* image = calloc(disk_size, sizeof(uint8_t));
if (image == NULL) {
return NULL;
}

const size_t sector_count = disk_size / sector_size;
for (size_t i = 0; i < sector_count; i++) {
uint8_t* sector = image + (i * sector_size);
size_t marker_size = 4;

memset(sector, 0xff, marker_size);
memset(sector + marker_size, 0x55, sector_size - marker_size);
}

return image;
}

static uint16_t select_superblock(uint16_t sector_size, size_t disk_size) {
assert(sector_size == 256 || sector_size == 512);

if (sector_size == 256) {
return DEFAULT_BUBBLE_SUPERBLOCK;
} else if (disk_size < 10 * 1024 * 1024) {
return DEFAULT_SUPERBLOCK;
} else {
return DEFAULT_HDD_SUPERBLOCK;
}
}

static bitmask_info_t calculate_bitmask_info(uint16_t sector_size, size_t disk_size) {
assert(sector_size == 256 || sector_size == 512);

const uint16_t superblock = select_superblock(sector_size, disk_size);

// Calculate bitmask.
const uint16_t sector_count = disk_size / sector_size;
const uint16_t required_bytes = sector_count / 8;
const uint16_t bytes_per_sector = sector_size == 512 ? BS512_BITMASK_SIZE : BS256_BITMASK_SIZE;
const uint16_t count = required_bytes / bytes_per_sector + 1;

const uint16_t bitmask = superblock - count;

const uint16_t tail_length = bytes_per_sector - (required_bytes % bytes_per_sector);
const uint16_t tail_offset = required_bytes % bytes_per_sector;

// Combine and return.
return (bitmask_info_t) {
.sector = bitmask,
.count = count,
.tail_length = tail_length,
.tail_offset = tail_offset,
};
}

static ccos_bitmask_list_t init_bitmask(ccos_disk_t* disk, bitmask_info_t info) {
// Initialize empty bitmask.
for (size_t i = 0; i < info.count; i++) {
ccos_bitmask_t* bitmask = (ccos_bitmask_t*)SECTOR(disk, info.sector + i);

memset(bitmask, 0x00, disk->sector_size);

bitmask->header.file_id = info.sector;
bitmask->header.file_fragment_index = i;
bitmask->allocated = 0;

if (i == info.count - 1) {
uint8_t* bitmask_bytes = get_bitmask_bytes(bitmask);
memset(bitmask_bytes + info.tail_offset, 0xff, info.tail_length);
}

update_bitmask_checksum(disk, bitmask);
}

// Build list of bitmask blocks.
ccos_bitmask_list_t bitmask_list = find_bitmask_blocks(disk);

// Mark the bitmask blocks as used in the bitmask itself.
for (size_t i = 0; i < info.count; i++) {
mark_block(disk, &bitmask_list, info.sector + i, 1);
}

return bitmask_list;
}

static ccos_date_t get_current_date() {
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
struct tm* time_struct;
time_struct = localtime(&tp.tv_sec);

return (ccos_date_t) {
.year = time_struct->tm_year + 1900,
.month = time_struct->tm_mon + 1,
.day = time_struct->tm_mday,
.hour = time_struct->tm_hour,
.minute = time_struct->tm_min,
.second = time_struct->tm_sec,
.tenthOfSec = tp.tv_nsec / 100000000,
.dayOfWeek = time_struct->tm_wday + 1,
.dayOfYear = time_struct->tm_yday + 1};
}

static void write_superblock(ccos_disk_t* disk, ccos_bitmask_list_t* bitmask_list) {
uint16_t id = disk->superblock_fid;

ccos_inode_t* root_dir = (ccos_inode_t*)SECTOR(disk, id);

memset(root_dir, 0x00, disk->sector_size);

root_dir->header.file_id = id;
root_dir->header.file_fragment_index = 0;

root_dir->desc.file_size = get_dir_default_size(disk);

root_dir->desc.name_length = 0;
memset(root_dir->desc.name, ' ', sizeof(root_dir->desc.name));

root_dir->desc.creation_date = get_current_date();
root_dir->desc.mod_date = root_dir->desc.creation_date;
root_dir->desc.expiration_date = (ccos_date_t) {};

root_dir->desc.dir_file_id = root_dir->header.file_id;

root_dir->desc.protec = 1;
root_dir->desc.pswd_len = 5;
root_dir->desc.pswd[0] = '\x29';
root_dir->desc.pswd[1] = '\xFF';
root_dir->desc.pswd[2] = '\x47';
root_dir->desc.pswd[3] = '\xC7';

root_dir->content_inode_info.header.file_id = id;
root_dir->content_inode_info.header.file_fragment_index = 0;
root_dir->content_inode_info.block_next = CCOS_INVALID_BLOCK;
root_dir->content_inode_info.block_current = id;
root_dir->content_inode_info.block_prev = CCOS_INVALID_BLOCK;

mark_block(disk, bitmask_list, id, 1);

uint16_t* content_blocks = get_inode_content_blocks(root_dir);
size_t max_content_blocks = get_inode_max_blocks(disk);

memset(content_blocks, 0xFF, max_content_blocks * sizeof(uint16_t));

uint16_t superblock_entry_block = id + 1;
content_blocks[0] = superblock_entry_block;

update_inode_checksums(disk, root_dir);

ccos_block_header_t* superblock_entry = (ccos_block_header_t*)SECTOR(disk, superblock_entry_block);
memset(superblock_entry, 0x00, disk->sector_size);
superblock_entry->file_id = id;
superblock_entry->file_fragment_index = 0;
((uint16_t*)superblock_entry)[2] = CCOS_DIR_LAST_ENTRY_MARKER;

mark_block(disk, bitmask_list, superblock_entry_block, 1);
}

static void write_boot_code(ccos_disk_t* disk, disk_format_t format, ccos_bitmask_list_t* bitmask_list) {
const uint8_t* boot_code = format == CCOS_DISK_FORMAT_GRIDCASE ? GRIDCASE_BOOT_CODE : COMPASS_BOOT_CODE;

size_t pages = BOOT_CODE_SIZE / disk->sector_size;
size_t offset = sizeof(ccos_boot_sector_t) / disk->sector_size;

for (size_t i = 0; i < pages; i++) {
memcpy(SECTOR(disk, offset + i), boot_code + i * disk->sector_size, disk->sector_size);
mark_block(disk, bitmask_list, offset + i, 1);
}
}

static void write_boot_sector(ccos_disk_t* disk, disk_format_t format, ccos_bitmask_list_t* bitmask_list) {
ccos_boot_sector_t boot_sector = (ccos_boot_sector_t) {
.superblock_fid = disk->superblock_fid,
.bitmap_fid = disk->bitmap_fid,
};

if (format == CCOS_DISK_FORMAT_GRIDCASE) {
memcpy(boot_sector.header, GRIDCASE_BOOT_SECTOR_HEADER, BOOT_SECTOR_HEADER_SIZE);
memcpy(boot_sector.boot_code, GRIDCASE_BOOT_SECTOR_CODE, BOOT_SECTOR_CODE_SIZE);
} else {
memcpy(boot_sector.header, COMPASS_BOOT_SECTOR_HEADER, BOOT_SECTOR_HEADER_SIZE);
}

size_t pages = sizeof(ccos_boot_sector_t) / disk->sector_size;
for (size_t i = 0; i < pages; i++) {
memcpy(SECTOR(disk, i), &boot_sector + i * disk->sector_size, disk->sector_size);
mark_block(disk, bitmask_list, i, 1);
}
}

int ccos_new_disk_image(disk_format_t format, size_t disk_size, ccos_disk_t* output) {
if (disk_size % 512 != 0) {
TRACE("Format image: image size %zu is not a multiple of 512", disk_size);
return EINVAL;
}

uint16_t sector_size = format == CCOS_DISK_FORMAT_BUBMEM ? 256 : 512;

uint8_t* data = new_empty_image(sector_size, disk_size);
if (data == NULL) {
return ENOMEM;
}

// InitializeMedia~Run~ gets the block numbers from the disk status.
// In the 2101 and 2102 firmwares, the bitmask and superblock numbers are hardcoded.
// Because we create disk images ourselves, we can choose any values and save them inside the image.
const uint16_t superblock = select_superblock(sector_size, disk_size);
const bitmask_info_t bitmask = calculate_bitmask_info(sector_size, disk_size);

ccos_disk_t disk = {
.sector_size = sector_size,
.superblock_fid = superblock,
.bitmap_fid = bitmask.sector,
.size = disk_size,
.data = data
};

ccos_bitmask_list_t bitmask_list = init_bitmask(&disk, bitmask);
write_superblock(&disk, &bitmask_list);

write_boot_sector(&disk, format, &bitmask_list);
write_boot_code(&disk, format, &bitmask_list);

*output = disk;

return 0;
}
26 changes: 26 additions & 0 deletions ccos_format.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef CCOS_DISK_TOOL_CCOS_FORMAT_H
#define CCOS_DISK_TOOL_CCOS_FORMAT_H

#include "ccos_disk.h"

#include <stdbool.h>
#include <stdlib.h>

typedef enum {
CCOS_DISK_FORMAT_COMPASS,
CCOS_DISK_FORMAT_BUBMEM,
CCOS_DISK_FORMAT_GRIDCASE,
} disk_format_t;

/**
* @brief Creates a new empty CCOS disk image.
*
* @param[in] format The disk format to use (e.g., DISK_FORMAT_COMPASS).
* @param[in] disk_size Total size of the image in bytes, should be a multiple of 512.
* @param[out] output Pointer to the resulting disk image structure.
*
* @return 0 on success, or an error code.
*/
int ccos_new_disk_image(disk_format_t format, size_t disk_size, ccos_disk_t* output);

#endif // CCOS_DISK_TOOL_CCOS_FORMAT_H
Loading