FMO/Mutex

Translated Document

FMO/Mutex

Reference: Memory Objects (External site)

Currently, this specification is heavily dependent on the Windows specification and is not intended for implementation on other operating systems.
Since shared memory and Mutex itself exist in other OSes, it would be desirable to define a separate specification.
If the specifications are decided, we will consider publishing them separately, so please contact Ukadoc Project.

Mutex

To indicate that the baseware is running, SSP holds a Mutex named "ssp", and Materia and CROW hold a Mutex named "sakura".
By checking for the presence of this named Mutex, you can determine whether it is running or not at a low cost.
The state of the Mutex itself is not determined, and there is no need to check whether or not it is in a signal state.

Example code for existence check only (C++)

HANDLE hmutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE,"ssp");
if ( hmutex ) {
  // Exists
  CloseHandle(hmutex);
}
else {
  // Does not exist
}

FMO (File Mapping Object)

All baseware maintains a named file mapping object (FMO) while running.
By reading this inter-process shared memory, the running ghost can be obtained at relatively low cost.
To avoid incomplete information while writing, exclusive control is performed by Mutex for FMO. Please check these together.

FMO name and character code

Saura: OS dependent, Shift JIS on Japanese OS
SakuraUnicode: Fixed to UTF-8 [SSP 2.5.26 or later]

FMO size

The first 4 bytes (bytes 0-3) indicate the size of the allocated FMO.
This is not the length of the information being written, but is a fixed value that indicates the allocated size of the FMO itself.
The value is currently fixed at 0x00010000 in little-endian, or 64kb.
To ensure compatibility with other programs, size changes are not currently considered.

Data body

The 4th and subsequent bytes are the main body of the FMO data. This will be in the following format.
(32-byte unique ID).(key name)[\1]value[\r\n]
[\1] is byte value 1, [\r\n] is CR_LF (line feed).
This format is repeated on multiple lines.

Data end

The end of the data is a byte value of 0, the same as for a C string.
Hence, the maximum size available for the data body is 65531 bytes (65536 - 4 - 1).
If you need to write to FOM and are about to exceed the size limit, be careful not to write incomplete information.
If the size is likely to be exceeded, it is preferable not to write a whole set of data. Even if this is not possible, do not end in the middle of a single line.

Example of data body

ssp_fmo_header_00004468_000f0dea.path[\1]D:\ssp\
ssp_fmo_header_00004468_000f0dea.hwnd[\1]986602
ssp_fmo_header_00004468_000f0dea.name[\1]Lache
ssp_fmo_header_00004468_000f0dea.keroname[\1]Tisse
ssp_fmo_header_00004468_000f0dea.sakura.surface[\1]0
ssp_fmo_header_00004468_000f0dea.kero.surface[\1]10
ssp_fmo_header_00004468_000f0dea.kerohwnd[\1]1052114
ssp_fmo_header_00004468_000f0dea.hwndlist[\1]986602,1052114
ssp_fmo_header_00004468_000f0dea.ghostpath[\1]D:\ssp\ghost\DE10_3001\
ssp_fmo_header_00004468_00120da6.path[\1]D:\ssp\
ssp_fmo_header_00004468_00120da6.hwnd[\1]1183142
ssp_fmo_header_00004468_00120da6.name[\1]Emily
ssp_fmo_header_00004468_00120da6.keroname[\1]Teddy
ssp_fmo_header_00004468_00120da6.sakura.surface[\1]20
ssp_fmo_header_00004468_00120da6.kero.surface[\1]10
ssp_fmo_header_00004468_00120da6.kerohwnd[\1]1117626
ssp_fmo_header_00004468_00120da6.hwndlist[\1]1183142,1117626,921002,2035340,658950
ssp_fmo_header_00004468_00120da6.ghostpath[\1]D:\ssp\ghost\emily4\
32-byte unique ID

This is a unique unique ID that indicates one group of ghosts. You must choose a string that is unique, at least within the FMO.
In many cases, some unique information is combined to obtain an MD5 hash or a combination of HWND (window handle).
Although the length is not specified in the Materia standard, it should be a fixed length of 32 bytes for compatibility.

Key name/value

A key indicating the type of information and the information body. It is as follows.

path
Full path to the root folder of the running baseware.
hwnd
Window handle of the main window, in decimal notation.
name
Same as sakura.name in descript.txt.
keroname
Same as kero.name in descript.txt.
sakura.surface
Surface ID currently displayed on the \0 side, in decimal notation.
kero.surface
Surface ID currently displayed on the \1 side, in decimal notation.
kerohwnd
The window handle for the \1 side window, in decimal notation. [SSP only]
hwndlist
Comma-separated list of all window handles currently in use, in decimal notation. [SSP only]
ghostpath
Full path of the running ghost. [SSP only]
fullname
Name entry in descript.txt of the running ghost. [SSP 2.5.58~]
modulestate
A comma-separated string that indicates the status of the group of modules loaded into the running ghost. [SSP 2.5.79~]
shiori:running SHIORI is loaded (if not, the string itself does not exist).
makoto-ghost:running MAKOTO on the ghost side is loaded.
makoto-shell:running MAKOTO on the shell side is loaded.
compatible:running Compatibility mode is selected and script.txt, etc., are loaded.
If there is an abnormality between SSP and the various modules, "critical" is written instead of "running".
Example: ssp_fmo_header_00004468_00120da6.modulestate[\1]shiori:running,makoto-ghost:running\r\n

Mutex for FMO

Since FMO itself does not have an exclusive control mechanism, a separate Mutex is maintained to avoid write/read conflicts.
The name will be FMO name + "FMO". For example:
FMO = Sakura : Mutex = SakuraFMO
FMO = SakuraUnicode : Mutex = SakuraUnicodeFMO
The determination of signal and non-signal status is important here.
When reading or writing, be sure to use WaitForSingleObject or an equivalent wait function to acquire ownership, and ReleaseMutex to release ownership when finished.

Old baseware may not support Mutex for FMO, so please do not generate an error if you fail to obtain Mutex.
In this case, please keep in mind the possibility of obtaining an incomplete FMO during writing, and write code that can handle this as safely as possible.

Code sample for reading and writing FMO (C++)

//Use CreateMutex instead for apps that should be retained, such as baseware
HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE,"SakuraFMO");

//Some baseware is not compatible with Mutex for FMO, so simply skip if not found
bool isWaitSuccess = true;

if ( hMutex ) {

    //If you wait with INFINITE, it will wait forever and the GUI will freeze, so be creative accordingly
    DWORD result = WaitForSingleObject(hMutex,INFINITE);
    
    if ( result != WAIT_OBJECT_0 ) {
        isWaitSuccess = false;
    }
}

if ( isWaitSuccess ) {

    //Apps that should be retained use CreateMutex instead
    HANDLE hFMO = OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,"Sakura");

    if ( hFMO ) {

        char *pDataStart = static_cast<char*>(MapViewOfFile(hFMO,FILE_MAP_ALL_ACCESS,0,0,0));

        if ( pDataStart ) {

            //The 4 bytes at the head are the FMO maximum size.
            //Note that this is different from string termination (zero termination of C strings).
            unsigned long length = *reinterpret_cast<unsigned long*>(pDataStart);

            char *pData = pDataStart;
            pData += 4;

            //****************************************
            //Do something with pData and length here
            //****************************************

            //Release MapViewOfFile
            UnmapViewOfFile(pDataStart);
        }
            
        //Open FMO handle
        //Keep apps that should be retained, such as baseware, without opening them
        CloseHandle(hFMO);
    }
}

if ( hMutex ) {
    if ( isWaitSuccess ) {
    
        //WaitForSingleObject causes Mutex to go to non-signal state, return to original state (Release)
        ReleaseMutex(hMutex);
    }

    //Finally, the Mutex handle is also released since it is not needed
    //Keep apps that should be retained, such as baseware, without opening them
    CloseHandle(hMutex);
}