-
Notifications
You must be signed in to change notification settings - Fork 1
File System
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 . |
Before we dive into each directory, let's look at some helper functions to simplify working with KOS's virtual filesystem.
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.
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.
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.
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.
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).
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
.
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()
:
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()
:
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.
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;
}
}
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;
}
void detach_file(char *filename) {
void *filedata = NULL;
ssize_t filesize = 0;
fs_ramdisk_detach(filename, &filedata, &filesize);
free(filedata);
}
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.
- 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).
- RAM Caching: Mounting the romdisk into RAM allows faster access than reading files from the CD.
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:
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.
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.
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.
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.
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.
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;
}
}