bible_documentations/_posts/software/file_system/2021-04-22-Fugue.md

39 KiB

layout title permalink author
page Fugue File System /software/fs/fugue.html Yatis

The Fugue File System

A few years ago, I tried to create my own operating system on fx8960, but during my implementation two "new" European calculator arrived in the marketplace: the Graph90+E and the Graph35+EII.

These calculators have been a good opportunity to port my OS on these machines. Unfortunately, my implementation of the Casio's file system doesn't work, and after some test I realize that they have changes their old file system to a new one called Fugue.

So, this is the starting point of this documentation.


Summary

  1. Fugue File System overview
  2. User Interfaces provided by Casio
    1. Bfile Interfaces
      1. Open primitive information
      2. File Descriptor
      3. Error code
    2. SD card interface
    3. SMEM interface
    4. Fugue interface
      1. Primitive information
      2. File Descriptor
      3. Error code
  3. Fugue Organisation and architecture
    1. File System table
    2. Bios Parameter Block
    3. Primitives table
    4. FAT
    5. Directory
      1. File
      2. Special
  4. Troubleshooting

Fugue File System overview

The Fugue File System was developed by Kyoto Software Research in 2004 (we can find some string in the Casio's operating system with this content: "Fugue FAT File System 2004-03-03 00:00"). This file system is based on the VFAT format, which is a basic format that stores and organizes files on NAND Flash memories.

Fugue exist in three formats (VFAT12/16/32) and it may exist in two types for each format: FX-CALC and PC. These types are used on calculators and on emulator respectively. I don't know how these two types differ, I only documented the FX-CALC type. Also, Casio's seems to have only implemented VFAT12 and VFAT16 formats, the table below show a small listing of calculators using Fugue with other information.

device name Fugue type available Format used Debug menu Storage Size Localisation String encoding
Graph35+EII FX-CALC/PC FAT12 No 3mo RAM ASCII/SHIFT-JIS
Graph90+E FX-CALC/PC FAT16 Yes 17mo RAM ASCII/SHIFT-JIS
Fxg10/20 FX-CALC/PC FAT16 Yes 17mo RAM ASCII/SHIFT-JIS

As you can see with the table above, this file system is not newer used by Casio because some old calculator such the fxcg10 created in March 2011 use this technology.


User Interfaces provided by Casio

The main interface is the mostly used and named Bfile. The Bfile "module" can be considered like a Virtual File System in which its role is only to redirect file operation (open, read, write, ...) to the appropriate interface. The schematic ahead shows the different interface that it's used by Casio to handler storage memories and file systems.

As you can see, we have three "part" that the Bfile interface abstract:

  • SMEM - Storage memory, flash
  • SD - Storage memory, SD card.

Why Casio's keep the old Bfile API while SD Card is no longer supported? I suspect that Casio want to hold a unified API for, probably, interoperability for all applications, which are probably developed separately. Also, Casio can add an SD Card support again and the OS is entirely prepared for. This is a conceptual good point to keep this architecture for the management of the storage memories abstraction whithout breaking anything.

Bfile Interfaces

Bfile is only a huge wrapper around two main module: SMEM for the flash memory and SD for the SD Card memory. All primitives provided by this module is mostly structured like this:

int bfile_primitive(int handle, ...)
{
	int deviceID;
	uint32_t SR;
	int retval;

	retval = IML_FILEERR_ILLEGALDEVICE;
	deviceID = (handle & 0x0f000000) >> 24;
	switch(deviceID) {
	case 1:
		SR = __internal_atomic_begin();
		handle &= 0xf0ffffff;
		retval = SMEM_primitive(handle, ...);
		__internal_atomic_end(SR);
		break;
	case 2:
		if (SD_isMounted() != 0)
			break;
		handle &= 0xf0ffffff;
		retval = SD_primitive(handle, ...);
		break;
	}
	return (retval);
}

Note that this structure is not superb because all file system are hard-coded (probably because the SD module need special preparation?), but its demonstrate that Bfile is only a wrapper for all storage memory module.

Also, the __internal_atomic_*() functions involved by Casio is only used when the Fugue file system is used, and block interruption setting the register SR.BL=1. The fact that interruption and exception are blocked is dangerous for Casio because any exception will crash the calculator. Besides, the old Casio's file system doesn't block interruptions.

Open primitive information

Let's discuss the open primitive. This is the entry point of this documentation, and I have traced a big part of it. So, how Bfile known which memory you want to interact with? The Bfile_OpenFile_OS(), which has been traduced in C language below, take a pathname as a parameter which must specify on which device you want to interact with. So, the pathname content a string with two information:

  • device name - The device name (should be fls0 the flash storage memory and crd0 for the SD storage memory).
  • path name - The pathname for a file (using SHIFT-JIS format).

For example, if I want a file in the flash memory I should use this strings: u"\\\\fls0\\filename.txt" and for the SD card memory: u"\\\\crd0\\filename.txt". Note that the letter 'u' before the string indicate that the string use two bytes per character.

int Bfile_OpenFile_OS(const uint16_t *pathname, int mode)
{
	int handle;

	switch (Bfile_identify_device(pathname)) {
	case 1:
		handle = SMEM_OpenFile(pathname, mode);
		if (handle < 0)
			break;
		handle &= 0xf0ffffff;
		handle |= 0x01000000;
		break;
	case 2:
		char pathname_cpy[0x10a * 2];
		Bfile_NameToStr_ncpy(pathname_cpy, pathname, 0x10a);
		if (SD_isMounted() != 0)
			break;
		handle = SD_OpenFile(pathname, mode);
		if (handle < 0)
			break;
		handle &= 0xf0ffffff;
		handle |= 0x02000000;
		break;
	default:
		handle = IML_FILEERR_ILLEGALDEVICE;
	}
	retrurn (handle);
}

For the old file system (not Fugue), the open mode is purely and simply ignored for the SMEM part, but seems to be used by the SD card module.

The Bfile can also manage other memory area, which there are reserved for OS's use, like EACT and MSC. You can find some debugging strings in old OSes.

==== First Format MCS =====
         (MCS&SYSRAM Area)
==== First Format drv0 ====
              (eAct Area)
==== First Format fls0 ====
             (Fugue Area)
Formatting...
====<< SPECIAL MODE >>====
2.INIT SYSRAM(MCS&SYSRAM)
3.INIT & FORMAT DRV0
4.INIT & FORMAT FLS0
    EXIT : Press [Clear]

However, if we analyze the Bfile_identify_device() sycall, which is involved by Bfile doesn't mention the drv0 device, so, I suppose that this feature is emulated by Casio(?), but I don't find any trace of the drv0 in recent OS. To dig.

File Descriptor

As you can see, in the code translation of the open primitive above, its generate a "handle" that can be related to "file descriptor" and we can determine that this object ig structured like this:

  • 0xAA000000 - unused, should be 0
  • 0x00BB0000 - Device ID (0x01=SMEM, 0x02=SD)
  • 0x0000CCCC - File System specific information

The Bfile handle should be used for all others Bfile primitives.

Error code

All Bfile primitives share common error code values, there is a list of all returned value that can be found in the filebios.h header in the Windows SDK.

value Desciption
0 IML_FILEERR_NOERROR
-1 IML_FILEERR_ENTRYNOTFOUND
-2 IML_FILEERR_ILLEGALPARAM
-3 IML_FILEERR_ILLEGALPATH
-4 IML_FILEERR_DEVICEFULL
-5 IML_FILEERR_ILLEGALDEVICE
-6 IML_FILEERR_ILLEGALFILESYS
-7 IML_FILEERR_ILLEGALSYSTEM
-8 IML_FILEERR_ACCESSDENYED
-9 IML_FILEERR_ALREADYLOCKED
-10 IML_FILEERR_ILLEGALTASKID
-11 IML_FILEERR_PERMISSIONERROR
-12 IML_FILEERR_ENTRYFULL
-13 IML_FILEERR_ALREADYEXISTENTRY
-14 IML_FILEERR_READONLYFILE
-15 IML_FILEERR_ILLEGALFILTER
-16 IML_FILEERR_ENUMRATEEND
-17 IML_FILEERR_DEVICECHANGED
-18 IML_FILEERR_NOTRECORDFILE
-19 IML_FILEERR_ILLEGALSEEKPOS
-20 IML_FILEERR_ILLEGALBLOCKFILE
-21 IML_FILEERR_DEVICENOTEXIST
-22 IML_FILEERR_ENDOFFILE
-23 IML_FILEERR_NOTMOUNTDEVICE
-24 IML_FILEERR_NOTUNMOUNTDEVICE
-25 IML_FILEERR_CANNOTLOCKSYSTEM
-26 IML_FILEERR_RECORDNOTFOUND
-27 IML_FILEERR_NOTDUALRECORDFILE
-28 IML_FILEERR_NOTALARMSUPPORT
-29 IML_FILEERR_CANNOTADDALARM
-30 IML_FILEERR_FILEFINDUSED
-31 IML_FILEERR_DEVICEERROR
-32 IML_FILEERR_SYSTEMNOTLOCKED
-33 IML_FILEERR_DEVICENOTFOUND
-34 IML_FILEERR_FILETYPEMISMATCH
-35 IML_FILEERR_NOTEMPTY
-36 IML_FILEERR_BROKENSYSTEMDATA
-37 IML_FILEERR_MEDIANOTREADY
-38 IML_FILEERR_TOOMANYALARMITEM
-39 IML_FILEERR_SAMEALARMEXIST
-40 IML_FILEERR_ACCESSSWAPAREA
-41 IML_FILEERR_MULTIMEDIACARD
-42 IML_FILEERR_COPYPROTECTION
-43 IML_FILEERR_ILLEGALFILEDATA

SD card interface

We don't really know how the SD module / file system works, but the interface still exists and syscalls too, probably for interoperability. For now, we don't have much information about this part if it is only a few syscalls that I was able to discover during my research on the Fugue file system.

Even the hardware module is unknown, we only have a list or all register without description. After some search, I have found the power initialization of the module, but that's all, the entire driver seems missing or not linked, and the file system is absent too.

%TODO: hidden debug menu
%TODO: syscall list.

SMEM interface

It may exist, only for prizm calculator, a public interface (using syscalls) that allow the user to interact directly to this module. For monochrome calculator, this separation exist, but cannot be acceded using syscalls, you must use Bfile.

On calculator which use the old file system, this module handled the storage memory on the NAND flash controller. But, if Casio's wanted to allow their calculators to be recognized as a USB key, they could no longer use their own file system for some technical reasons. This is probably why, Fugue has been introduced because it's a true Virtual FAT file system that can easily be used for the USB's Mass Storage protocols.

The SMEM just wrap the Fugue API, this is a typical function organization that this module do:

int SMEM_primitive(int handle, ...)
{
	struct smem_file_information fileinfo;
	int fugue_handle;
	int retval;

	/* wipe internal buffer */
	__internal_memset(fileinfo, 0x00, sizeof(struct smem_file_information));

	/* Directly draws the busy-indicator(hourglass) in the upper
	   right corner of the screen (withou using VRAM). */
	HourGlass();

	/* check the handle validity usig the SMEM file descriptor table */
	if (SMEM_fd_get_fileinfo(handle, &fileinfo) != 0)
		return (IML_FILEERR_ILLEGALPARAM);

	/* involve the "real" Fugue primtive */
	retval = Fugue_primitives(&fileinfo, ...);
	retval = SMEM_convert_fugue_returned_value_into_bfile_code(retval);

	/* return the status code */
	retrun (retval);
}

This pattern is interesting because it shows us that Fugue have not the same error code list than Casio. We will discuss this later.

I have found another interesting information in the open primitive:

int SMEM_OpenFile(const uint16_t *pathname, int mode)
{
	struct smem_file_information {
		char pathname_cpy[540];
		char real_pathname[540];
	} fileinfo;

	/* wipe internal buffer */
	__internal_memset(fileinfo, 0x00, sizeof(struct smem_file_information));

	/* Directly draws the busy-indicator(hourglass) in the upper
	   right corner of the screen (withou using VRAM). */
	HourGlass();

	/* syscall %dd2 on fxcg50, convert FONTCHARACTER (uint16_t) into a
	   compresed string AND skip the first '\' character AND check pathname
	   validity. Very specific one. */
	FONTCHARACTER_convert_to_fugue_format(pathname, fileinfo.real_pathname);

	/* convert the Casio's open mode into Fugue open mode (seems broken
	   in the real OS) */
	mode = 0;
	if ((mode & 0x03) == 0x01)	// _OPENMODE_READ
		mode = 0x09;
	if ((mode & 0x03) == 0x00)	// _OPENMODE_READ_SHARE
		mode = 0x09;
	if ((mode & 0x03) == 0x02)	// _OPENMODE_WRITE
		mode = 0x0a;
	if ((mode & 0x03) == 0x03)	// _OPENMODE_READWRITE
		mode = 0x0c;
	if ((mode & 0x80) != 0)		// SHARED flags
		mode |= 0x20;

	/* involve the "real" Fugue open primtive */
	retval = Fugue_primitives(&fileinfo, );
	retval = SMEM_convert_fugue_returned_value_into_bfile_code(retval);
	if (retval != IML_FILEERR_NOERROR)
		return (retval);

	/* register these information into the internal file descriptor table
	   that will save and generate a the handle */
	memcpy(fileinfo.pathname_cpy, pathname);
	retval = SMEM_descriptor_table_register(retval, fileinfo.pathname_cpy);

	/* return the status code */
	retrun (retval);
}

Obviously, with this snippet, we can find that the open mode is not the same between SMEM and Fugue and Casio's seems useful to not set up a system call to perform mode translation, but a syscall to translate the returned value seems legit to Casio. Why not.

Bfile Fugue mode name (Bfile)
0x01 || 0b0000 0001 0x09 || 0b0000 1001 _OPENMODE_READ
0x80 || 0b1000 0001 0x29 || 0b0010 1001 _OPENMODE_READ_SHARE
0x02 || 0b0000 0010 0x0a || 0b0000 1011 _OPENMODE_WRITE
0x03 || 0b0000 0011 0x0c || 0b0000 1100 _OPENMODE_READWRITE
0x83 || 0b1000 0011 0x2c || 0b0010 1100 _OPENMODE_READWRITE_SHARE

Also, since Fugue has been introduced, the SMEM module is a simple dirty wrapper around Fugue primitives. I said, "a dirty wrapper" because Casio's seems to keep their file descriptor table, a huge table of 16 slots, which are structured like this:

offset size (byte) description
0 4 Fugue handle
4 540 Filename, uint16_t format

Each slot takes 544 bytes, and we can open 16 files simultaneously which gives us a total of ~8ko only to store information that is already saved by Fugue, because yes, Fugue has its own file descriptor table too.

Fugue interface

Curiously, the Fugue and SMEM interface are correctly isolated to each other, and the SMEM just wraps the primitives that Fugue provides. It may not exist any user interface that can be used to interact directly with Fugue primitives.

This is why I have tried to discover how the Fugue file system is organized and if this documentation has seen the day, it's because I have enough information to ignore Fugue primitives and use my own implementation to interact with the storage memory. I will explain all I have found about in the next chapter.

Primitive information

It's important to notify that the SMEM interface wrap Fugue's primitives, and no user access is provided to use this part. Despite the interaction, I think that it's interesting to see how Casio's do the job because we can find some interesting information. As we can see above, there is a wrapper around what I have called "real Fugue primitive", it's not true. The primitive involved is another wrapper to "prepare" the true one primitive.

//---
// Seems to be Casio's file system API
//---

/* casio_get_bios(): Return the Bios Parameter Block of an file system */
static void *casio_get_bios(int filesystemID)
{
	strcut casio_fs *fs = (void*)__CASIO_FS_TABLE_ADDRESS;

	/* check the file system ID validity */
	if (filesystemID < 0 || filesystemID >= __CASIO_FS_NB_REGISTERED)
		return (NULL);

	/* check the file system validity */
	if (fs[filesystemID].is_mounted != 1)
		return (NULL);

	/* return the file system information */
	return (fs[filesystemID].bios_parameter_block);
}

//---
// Internal fugue handle helper
//---
static void *__fugue_handle_get_bios(int handle)
{
	return (__casio_get_bios(handle & 0x7f000000) >> 24));
}
static int __fugue_handle_get_fileidx(handle)
{
	return ((handle & 0x0001ffff) >> 0);
}

//---
// Fugue bios helper
//---
static int __fugue_bios_check_validity(struct fugue_bios *bios)
{
	return ((bios->configuration & 0x00000003) == 0);
}

//---
// "Real" primitive (table ?)
//---

/* fugue_real_primitive(): Real primitive */
static int fugue_real_primitive(struct fugue_bios *bios, int fileID, ...)
{
	// check arg validity

	/* check the file system configuration */
	if (__fugue_bios_check_validity(bios) != 0)
		return (IML_FILEERR_ILLEGALFILESYS); // -1

	...
	..  primitve work
	...
}

/* fugue_primitive(): Wrap the "real" primitve function */
int fugue_primitive(int handle, ...)
{
	void *bios;
	int fileID;

	// somtime, perform extra argument check

	/* get handle information */
	bios   = __fugue_handle_get_bios(handle);
	fileID = __fugue_handle_get_fileidx(handle);

	/* involve the real primitive */
	return (fugue_real_primitive(bios, fileID, ...));
}

This is a typical example of all implemented Fugue function. With this wrapping method, Casio's just fetch some information before starting the job.

Also note that important information are hard-coded, like the number of registered file system and the address of the internal FS table. But, don't worry I will explain all important information and this snippet will be clearer.

File Descriptor

The file descriptor generated by Fugue is structured like this:

  • 0x7F000000 - Device ID (from the mounted table, "fls0" is 0 and "crd" is 1)
  • 0x00FE0000 - Device watermark (for the "fls0" the watermark is 0x01)
  • 0x0001FFFF - File index in the file descriptor table

This handle should be used for all other Fugue primitives. Note that user is not authorized to interact directly with the file system, so this information is not really useful.

Error code

The syscall %dd1: SMEM_convert_fugue_returned_value_into_bfile_code translates all Fugue returned value into Bfile error code. So, I have dumped all original Fugue code and add the Bfile macros name.

id Desciption
-1 IML_FILEERR_ILLEGALSYSTEM
-2 IML_FILEERR_ILLEGALPARAM
-3 IML_FILEERR_DEVICEERROR
-4 IML_FILEERR_NOERROR
-5 IML_FILEERR_ACCESSDENYED
-6 IML_FILEERR_ILLEGALPARAM
-7 IML_FILEERR_NOTMOUNTDEVICE
-8 IML_FILEERR_ILLEGALPATH
-9 IML_FILEERR_ALREADYEXISTENTRY
-10 IML_FILEERR_ILLEGALPATH
-11 IML_FILEERR_DEVICEFULL
-12 IML_FILEERR_ILLEGALFILESYS
-13 IML_FILEERR_DEVICEFULL
-14 IML_FILEERR_ENTRYNOTFOUND
-15 IML_FILEERR_ILLEGALPARAM
-16 IML_FILEERR_ACCESSDENYED
-17 IML_FILEERR_ACCESSDENYED
-18 IML_FILEERR_ILLEGALPARAM
-20 IML_FILEERR_ILLEGALFILESYS
-40 IML_FILEERR_ILLEGALFILESYS
-99 IML_FILEERR_ILLEGALFILESYS

Fugue Organisation

Fugue is a real implementation of the VFAT file system. I say that because I have recently discovered the "real" Bios Parameter Block with a lot of information. Enough information to by-pass the entire Fugue drivers. But, I will describe how the flash memory is organized in a second time, when I will have verified and checked all of my hypothesis.

Of course, I don't have discovered all information about the mechanisms of how Fugue works because its represents a huge part of the OS, but I have enough information to perform a pseudo-file listing of the root directory.

Also, some users have a bad time with the file system abstraction that has definitively broken their calculator. So, this documentation is important to know the limit of this technology and how to avoid these inconveniences.

So, the diagram above (you can click on the image to have the full-size), represent all information that I have gleaned during many weeks of disassembly. I will explain each part of the image in this chapter.

All of this information was in RAM at non-constant address, but it's absolutely localizable manually, this mean that you can easily write your own primitive to find the first block of information and handle all Fugue information, but probably read-only, you will not be able to handle the "write part" only with this information.

File System table

Let's start with...Casio's information because before starting to explain Fugue data structures, we need to know how to find this information. During my reverse engineer on the Bfile_OpenFile_OS() primitive the first thing that I met is the mechanism used by Casio to find if a file system exist. This routine has been translated upper in this topic, but let's look closer:

/* casio_get_bios(): Return the Bios Parameter Block of an file system */
static void *casio_get_bios(int filesystemID)
{
	strcut casio_fs *fs = (void*)__CASIO_FS_TABLE_ADDRESS;

	/* check the file system ID validity */
	if (filesystemID < 0 || filesystemID >= __CASIO_FS_NB_REGISTERED)
		return (NULL);

	/* check the file system validity */
	if (fs[filesystemID].is_mounted != 1)
		return (NULL);

	/* return the file system information */
	return (fs[filesystemID].bios_parameter_block);
}

Before I detail what is the struct casio_fs, I just want to notify that, the file system table is hard-coded and points to RAM and from as far as I know, it doesn't exist any syscall or function that return this address.

Moreover, this is the same story for the number of registered file system that is hard-coded many times in the OS. This is the way I think that these two information are probably macros for the compiler.

The fact that the number of file system are hard-coded lets think that these part of the OS, in risk of complicating our task if we want to find the table's address. But it is not impossible if we understand what is stored in the casio_fs structure.

offset Size (bytes) Description Fugue information Checked by Casio
+0 4 Bios Parameter Block RAM address Yes
+4 4 Unknown 0x00000002 No
+8 4 Bfile handle magic ID 0x00000001 No
+12 20 (ascii) device name "fls0" Yes
+32 18 (ascii) root name "\" Yes
+50 1 mounted flags 1=yes, 0=no Yes
+51 1 FS id 0 No

The table above is the complete description of all fields that compose any file system information.

If we want to find this block we can first check if we can find the "fls0" device name because this name is here since the night of time (we can find this name in the first version of the Casio's operating system). So, you are almost certain that this information is content. After finding the string, check if the root names is present, then the mounted flags, then check if the Bios Parameter Block is non-null.

This is personally what I used to determine the localization of this table and I and I can reliably find it on any OS using Fugue. (TODO: determine if the old file system use the same mechanism for the file system).

Note that all information described in this topic cannot be affirmed, and it is only supposition. For example, I'm not sure that the FS id is really the file system ID because it is never used nor checked.

Bios Parameter Block

The Bios Parameter Block is not a bios parameter block, but it has the same role, this is why I called it like that.

A "real" bios parameter block is used by the FAT file system as a data structure in the volume boot record describing the physical layout of a data storage volume. Here, it only contents information about data structure and internal cached information. Fugue use this block to store their information used to find files, calculate quickly the address of this or this thing....in short, this is like a data descriptor and a personal cache for the file system.

offset Size (bytes) Description Fugue information
+0 4 Configuration file system configuration bitmap
+4 4 sector size 512
+8 4 number of sector per cluster 8
+12 4 sector starting ID unstable
+16 4 unknown unstable(?)
+20 4 unknown unstable(?)
+24 4 unknown unstable(?)
+28 4 unknown unstable(?)
+32 4 number of file per stage 512
+36 4 unknown unstable(?)
+40 4 number total of cluster unstable
+44 4 number total of free cluster unstable
+48 4 unknown unstable(?)
+52 4 root sector ID unstable
+56 4 address of the "meta" table RAM address, generated only if needed
+60 4 address of the ?????? table RAM address, generated only if needed
+64 4 address of the file descriptor table RAM address, generated only if needed
+68 4 unknown unstable(?)
+72 4 address of the FAT information RAM address
+76 4 address of the primitives table RAM address
+80 4 unknown unstable(?)
+84 2 number of cluster per stage 4
+86 2 unknown unstable(?)
+88 4 unknown unstable(?)
+92 4 (SHIFT-JIS) separator name u"\"
+96 5 (ASCII) device name "fls0"

Much information are missing, but most important are documented with this table. So, we can quickly know:

  • the device size: (number_sector_per_cluster * sector_size) * number_total_of_cluster
  • the space left: (number_sector_per_cluster * sector_size) * number_total_of_free_cluster
  • how the file system is configured for (see below)
  • the device name
  • data layout: (sector size, number of sector per cluster, ...)

Furthermore, some information like the number of file per stage can be easily calculated if we know that each sector are divided in "directory" of 32 bytes: ((number_sector_per_cluster * sector_size) * number_of_cluster_per_stage) / 32 but I will give you more information about the structure of file / directory in the next point.

Few note about the configuration. The first information that we can get is the file system design:

  • configuration & 0x00000003 -> 1=VFAT12,2=VFAT16,3=VFAT32
  • configuration & 0x00030000 -> 1=FX-CALC,2=PC
  • configuration & 0x03000000 -> File handle magic mark

I really don't know how much the FX-CALC version and the PC version differ because I don't reverse engineer the emulator where this version is used, but I suppose that is only the way to manage the physical storage memory. Be careful if you encounter this format.

Also, the "file handle magic mark" is only used by Fugue when its generate their handles. This is not important here.

The file system format (VFAT12/16/32) change nothing about how to handle Fugue information. Some will tell me that the Graph35+EII is probably not VFAT because the name is limited to 13 characters. Wrong, on this calculator it's only Casio that limit the size of the file name, not Fugue.

Primitives table

This is a simple table which content a lot of pointers to function. These primitives are involved only internally by Fugue, and I don't know information about any of them.

All I know is that it may exist a primitive for:

  • creating the file descriptor table
  • formating the file system
  • creating the FAT information
  • find the next cluster

So, if you whant to be be Fugue compilant whithout too much difficulty, I advise you to look in this direction.

FAT

Let's take a look now at the address of the FAT information field from the previous information table. This address point to a small structure:

offset Size (bytes) Description
+0 4 first cluster address
+4 4 number of sector that rest in cluster
+8 4 current cluster ID
+12 4 current sector ID

All information except the cluster address should not be modified. During my implementation of my own Fugue primitive I have tried to update this information, and it seems that I have missed something. Result? The calculator almost died (see the chapter Troubleshooting for more information and supposition on how it arrived) and, fortunately for me, I was doing this on my fxcg50 calculator which has a "special hidden" menu which allow performing a low-level format which has erased all storage memory before more damage.

Back on topic, the cluster is organized like this in the RAM memory:

The sector header is important here and it is composed of 8 bytes: | offset | Size (byte) | Description | | +0 | 4 | sector ID | | +4 | 2 | unknown | | +6 | 2 | unknown | If you want to list the root directory you simply have to find the root sector ID. But, How to find this information ? Using the Bios parameter block. You have just to add the sector starting ID and the root sector ID then walk through the first cluster and find the sector with the same ID.

Directory

Now, you can start walking through the sector. As explained by the schematics below, sector are just a fixed table that content block of 32 bytes. Each block is called directory and because we use the Virtual FAT file system they exist to type of directory: file and file name fragment.

File

The "file" directory content only file "meta" information, like its size, type, date time (normally, but visibly, Fugue doesn't set the date information), extension and the cluster ID that is used to get its data.

offset Size (bytes) Description
+0 8 (DOS) filename
+8 3 (DOS) extention
+12 1 (DOS) attribute
+13 7 unknown
+20 1 cluster ID part 3
+21 1 cluster ID part 4
+22 4 unknown
+26 1 cluster ID part 2
+27 1 cluster ID part 1
+28 4 file size

The table above show all information that I have found for this type of directory, but to be honest I'm not able yet to handle cluster correctly, and I don't have revers-engineering the Bfile_ReadFile_OS() primitive to find out all information about this type of directory.

According to the FAT file system standard file attribute (or file type), I have listed, which is handled by Fugue or not.

offset Description Implemented
0x01 read-only not implemented
0x02 hidden not implemented
0x04 system not implemented
0x08 volume label implemented, ignored
0x0f special VFAT long file name
0x10 sub-directory implemented
0x20 archive implemented
0x40 device implemented
0x80 custom not implemented

I have noticed that all file transmitted by USB are automatically flagged like an archive and I don't know how this particular file are handled.

I may exist a special signification for the first by the file name. The table ahead show you the meaning of these fields:

value (hex) Description
0x00 This directory and all directory after this block are free
0x20 This directory is empty
0x05 This directory is removed
0xe5 This directory is removed (file name fragment only ?)

file name fragment

This is probably the most complet part that I have documented. Unfortunately for me, this is the exact data structure used by the VFAT file system...so, the structure is already documented since...1977. Nice.

offset Size (bytes) Description
+0 1 filename block information
+1 2 (SHIFT-JIS, MSB) character 0
+3 2 (SHIFT-JIS, MSB) character 1
+5 2 (SHIFT-JIS, MSB) character 2
+7 2 (SHIFT-JIS, MSB) character 3
+9 2 (SHIFT-JIS, MSB) character 4
+11 1 attribute (should be 0x0f)
+12 1 padding (should be 0x00)
+13 1 checksum
+14 2 (SHIFT-JIS, MSB) character 5
+16 2 (SHIFT-JIS, MSB) character 6
+18 2 (SHIFT-JIS, MSB) character 7
+20 2 (SHIFT-JIS, MSB) character 8
+22 2 (SHIFT-JIS, MSB) character 9
+24 2 (SHIFT-JIS, MSB) character 10
+26 2 unknown
+28 2 (SHIFT-JIS, MSB) character 11
+30 2 (SHIFT-JIS, MSB) character 12

The structure seems exactly the same that the VFAT, but I doubt about the offset +26 (which is normally cluster information) because I don't have documented the cluster part of Fugue.

However, I have documented the checksum part. The checksum of a file name directory is the checksum of the next block (which can be another file name fragment or the file "meta" block). This mechanism exists to ensure the validity of a "linked" file information, which is composed of X file name fragment and a file directory.

/* fugue_calculate_checksum(): Calculate the directory checksum */
static uint8_t fugue_calculate_checksum(void *directory)
{
	uint8_t *block;
	uint8_t checksum;
	int a;
	int b;

	checksum = 0;
	block = directory;
	for (int i = 0 ; i < 11 ; ++i) {
		checksum = checksum & 0xff;
		a = checksum / 2;
		b = checksum * 128;
		checksum = a | b;
		checksum = checksum + block[i];
	}
	checksum = checksum & 0xff;
	return (checksum);
}

Upper, you can find my implementation that works to calculate the checkum of the Fugue directory.

Now you know that exist special directory which store the entire file name, and we also know that these directories are linked with a checksum. So, how these blocks are really organized? The first bytes of the directory is structured like this:

  • info & 0x40 -> first filename fragment flags (last part of the filename)
  • info & 0x0f -> filename fragment index

To schematize the concept, imagine the long filename <<elle_repondait_au_nom_de_Bella.elf>>, the organization will be like this:

With all this information, I can write my own implementation of the Fugue file system to perform a listing of the root directory!


That's it, this is all I have discovered for about the Fugue file system. Cluster handling is missing, but I'm trying to find out information on this.

I wish to be able to be completely independent with this part of the OS :)


Troubleshooting