Skip to content

File System

Andy Barajas edited this page Nov 10, 2024 · 1 revision

Introduction

This guide focuses on KOS's Virtual Filesystem (VFS). Understanding KOS's filesystem structure is helpful before starting any project. KOS has several mount points:

Directory Read Write Description
/cd Contains files that exist on the CD currently in the Dreamcast.
/pc Links back to your computer if your Dreamcast is connected (e.g., serial cable, BBA/LA).
/vmu Contains all the VMUs currently plugged in.
/ram Contains files attached to the ramdisk.
/rd Contains the contents of the romdisk statically linked with the executable.
/pty Implements a pseudo-terminal filesystem (similar to Linux's /dev/pty).
/sd Appears when you successfully mount the SD card to /sd.

Helper Functions

Before we dive into each directory, let's look at some helper functions to simplify working with KOS's virtual filesystem.

fs_copy

ssize_t fs_copy(const char *src, const char *dst);

The fs_copy() function copies the file at src to dst on the filesystem and returns the file size. You must be able to write to the dst base directory. You would mainly use this to cache files from /cd to /ram.

fs_load

ssize_t fs_load(const char *src, void **out_ptr);

The fs_load() function reads a whole file into RAM, returning the data size and a pointer to the data in RAM.

fs_path_append

ssize_t fs_path_append(char *dst, const char *src, size_t len);

The fs_path_append() function appends a path, ensuring a / character between dst and src. It returns the length of the new string (including the NUL terminator). len is the size of the dst buffer. It is used to make sure we stay in bounds of the dst buffer when appending a path.


Directory Descriptions

CD

Contains files from the CD. You can open a maximum of 8 files at once in /cd.

One thing to keep in mind when constructing a self bootable Dreamcast disc is that the location of a file on the CD affects the reading speeds of that file. This is because the GDROM in the Dreamcast works in constant angular velocity (CAV) mode. So files that exist on the outer sections of the CD are read faster (12x @ ~2 MB/sec) than the inner sections of the CD. Add a dummy file named "0.0" to push data to edge of the disk for faster reads.

PC

This directory links back to your computer if your Dreamcast is somehow linked to your computer (i.e. serial cable, BBA/LA). This directory is available when using DC-Load/DC-Tool combo to upload software to the Sega Dreamcast. By default, /pc points to the root directory of your computer. Root being:

  • C:// on Windows
  • Macintosh HD on Mac To change the directory /pc points to, you can use the -c option in DC-Tool like so:
dc-tool-ip -t <IP Address> -c /path/to/mount/pc -x game.elf

I recommend that you use -c . to point to the current directory in your terminal.

You must be a super user. For Windows, ''run as administrator''. For Mac users, use the sudo command (sudo dc-tool-ip -t.. etc).


VMU

This directory contains folders representing VMUs with the letternumber naming convention. The letter stands for which port the controller that the VMU is plugged into (a,b,c, or d) and the number stands for which slot the VMU is in (1 or 2).

Port a Port b Port c Port d
Slot 1 a1 b1 c1 d1
Slot 2 a2 b2 c2 d2

Inside these folders are game saves, VMU games, etc. Creating/Saving files to the VMU require special file headers that are covered in another tutorial. However, you can easily create a backup of your VMU files using fs_copy() and saving it to /pc or /sd.

Ramdisk

You only have one ramdisk available, and it's mounted on /ram. This directory is where you can cache many single files from the CD for faster reading access later on. Remember that each file you attach eats up RAM, so be sure to detach any files you are not using. You can have a maximum of 8 files open at one time in /ram.

There are two functions you can use to "attach" a file to the ramdisk, fs_copy() and fs_ramdisk_attach():

fs_ramdisk_attach

int fs_ramdisk_attach(const char *fn, void *obj, size_t size);

The fs_ramdisk_attach() function takes a block of memory and associates it with a file on the ramdisk.

To release or detach a file from the /ram directory, you must use fs_ramdisk_detach():

fs_ramdisk_detach

int fs_ramdisk_detach(const char *fn, void **obj, size_t *size);

The fs_ramdisk_detach() function retrieves the block of memory associated with the file, removing it from the ramdisk. You are responsible for freeing obj when you are done with it. Remember to fclose() any file before you detach it.

Examples of each function are shown in the code snippets below.

Attach/Detach Examples

Attach File

bool attach_file(char* filepath) {
    void *filedata = NULL;
    char *filename = basename(filepath);
    ssize_t filesize = fs_load(filepath, &filedata);

    if(filesize != -1) {
        fs_ramdisk_attach(filename, filedata, filesize);
        return true;
    } 
    else {
        return false;
    }
}

Alternative Attach Method

bool attach_file_v2(char *filepath) {
    char new_path[256] = {0};
    char *filename = basename(filepath);
    fs_path_append(new_path, "/ram", 256);
    fs_path_append(new_path, filename, 256);
    ssize_t filesize = fs_copy(filepath, new_path);

    return filesize > 0;
}

Detach File

void detach_file(char *filename) {
    void *filedata = NULL;
    ssize_t filesize = 0;

    fs_ramdisk_detach(filename, &filedata, &filesize);
    free(filedata);
}

Romdisk

Romdisk is a small read-only filesystem. You can think of a romdisk as a single file (or folder) that contains other files. Each romdisk should be organized to contain assets that pertain to a specific level or stage, making it easier to manage load/unload operations. You can have a maximum of 16 files open across all mounted romdisks at one time.

Advantages of Using a Romdisk

  1. Efficient Loading: Reading a single file from the CD is faster than reading many spread-out files. A romdisk is a collection of files in a single file, making it efficient to read all at once. You can also gzip the romdisk for faster CD reads, which you may want to do if it contains uncompressed data files (e.g., WAV, KMG/DTEX, BMP).
  2. RAM Caching: Mounting the romdisk into RAM allows faster access than reading files from the CD.

Creating a Romdisk

Creating a romdisk is typically done within a makefile using:

$(KOS_GENROMFS) -f <RomdiskFileName> -d <RomdiskFolderPath> -v

Create a folder with assets for each romdisk, then run the above command for each romdisk, specifying a unique <RomdiskFileName> and <RomdiskFolderPath>. After creation, you have two mounting options:

1. Manual Mount/Unmount (Recommended)

This approach allows you to mount multiple romdisks at different mountpoints using fs_romdisk_mount(). Ensure to unmount unused romdisks with fs_romdisk_unmount(), and fclose() any open files associated with an unmounting romdisk.

fs_romdisk_mount

int fs_romdisk_mount(const char *mountpoint, const uint8_t *img, int own_buffer);

The fs_romdisk_mount() function mounts a romdisk image in memory at the specified mountpoint (e.g., "/stage1"). Pass 1 to own_buffer so the memory is freed upon unmount.

fs_romdisk_unmount

int fs_romdisk_unmount(const char *mountpoint);

The fs_romdisk_unmount() function unmounts a previously mounted romdisk. If own_buffer was 1, the memory is freed automatically.

2. Statically Link Romdisk

This approach links the romdisk directly to the executable, mounting it at /rd. Statically linked romdisks always occupy RAM and are primarily used for quick demos. Only one romdisk can be statically linked.

Statically Linking a Romdisk in a Makefile

TARGET = main.elf
OBJS = main.o romdisk.o 
KOS_ROMDISK_DIR = romdisk

all: rm-elf $(TARGET)

include $(KOS_BASE)/Makefile.rules

KOS_LOCAL_CFLAGS = -I$(KOS_BASE)/addons/zlib
	
clean: rm-elf
	-rm -f $(OBJS)
	
rm-elf:
	-rm -f $(TARGET) romdisk.*

$(TARGET): $(OBJS) 
	kos-cc -o $(TARGET) $(OBJS)

run: $(TARGET)
	$(KOS_LOADER) $(TARGET)

dist: $(TARGET)
	-rm -f $(OBJS) romdisk_boot.img
	$(KOS_STRIP) $(TARGET)

This mounts the statically linked romdisk at /rd until the program exits.


Example: Mounting a Romdisk

bool mount_romdisk(char *filepath, char *mountpoint) {
    void *buffer = NULL;
    ssize_t filesize = fs_load(filepath, &buffer);

    if(filesize != -1) {
        fs_romdisk_mount(mountpoint, buffer, 1);
        return true;
    } 
    else {
        return false;
    }
}