libslinga API Feedback

slinga

Established Member
I would appreciate some feedback on the API for libslinga before I get too far along.

libslinga: Saturn LIbrary for Navigating Gaming Achievements

libslinga is a Sega Saturn library for working with save game files. The goals are:
- work on real hardware
- support as many backup devices as possible (internal memory, cartridge, floppy, ODEs, etc)
- provide a shim layer for replacing Sega's BUP library
- support Cafe-Alpha's .BUP file format by default and seamlessly to the user. Have a flag to allow for skipping the BUP file format
- MIT license for easy integration with Jo Engine, YAUL, and/or any other Saturn frameworks. ODE menus can also easily use this library

For the shim layer I'm envisioning replacing the BUP library pointer at 0x06000354 with the shim. This would require an AR Cheat Code functionality. Once shimmed:
- Cartridge support could be replaced seamlessly with floppy or ODE support
- No limit to cartridge storage space. The support sizes of cartridges are stored in the BUP library. Replacing BUP lib means we can do whatever want.
- Development of Pseudo Saturn Kai or similar cart that can support both ram expansion and direct save. Honestly not sure if this is already a thing.

Here's my api so far:
C:
#pragma once

#define TRUE  1
#define FALSE 0

#define MAX_SAVE_SIZE           (256 * 1024) // according to Cafe-Alpha this is the maximum size supported by the BIOS
#define MAX_SAVE_NAME           12 // save name as seen in the BIOS
#define MAX_SAVE_COMMENT        11 // save comment as seen in the BIOS
#define MAX_FILENAME            32 // filename as stored on the device. Not the same as the SAVE_NAME on certain devices
#define MAX_SAVES               255

// all devices should standardize on this directory
// for storing saves
#define SAVES_DIRECTORY "SATSAVES"

// backup mediums supported by SLiNGA
typedef enum
{
  INTERNAL = 0, // on-board sega saturn memory
  CARTRIDGE = 1,
  SERIAL = 2,
  ACTION_REPLAY = 3,

  // ODEs?
  // CD?

  MAX_MEDIUM_DEVICE,
} BACKUP_MEDIUM_TYPE;

typedef enum
{
  SLINGA_SUCCESS = 0,

  SLINGA_NOT_INITIALIZED = 1,
  SLINGA_INVALID_BACKUP_MEDIUM_TYPE = 2,
  SLINGA_BUFFER_TOO_SMALL = 3,
  SLINGA_DEVICE_NOT_PRESENT = 4,
  SLINGA_NOT_FORMATTED = 5,


} SLINGA_ERROR;

// meta data related to save
typedef struct  _SAVE_METADATA {
    char filename[MAX_FILENAME];    // filename on the medium. Will have .BUP extension on CD FS and ODEs.
    char name[MAX_SAVE_NAME];       // save name as seen in the BIOS
    char comment[MAX_SAVE_COMMENT]; // save comment as seen in the BIOS
    unsigned char language;         // language of the save
    unsigned int date;              // save modified time in number of minutes since Jan 1, 1980
    unsigned int data_size;         // size of the save data in bytes
    unsigned short block_size;      // number of blocks used by the save
} SAVE_METADATA, *PSAVE_METADATA;

// total and available size of the backup medium
typedef struct _BACKUP_STAT
{
   unsigned int total_size_bytes;
   unsigned int total_blocks;
   unsigned int block_size;
   unsigned int free_size_bytes;
   unsigned int free_blocks;
   unsigned int max_saves_possible; // maximum

} BACKUP_STAT, *PBACKUP_STAT;


// libslinga API
SLINGA_ERROR Slinga_Init(void);
SLINGA_ERROR Slinga_IsPresent(BACKUP_MEDIUM_TYPE type);
SLINGA_ERROR Slinga_IsReadable(BACKUP_MEDIUM_TYPE type);
SLINGA_ERROR Slinga_IsWriteable(BACKUP_MEDIUM_TYPE type);
SLINGA_ERROR Slinga_Stat(BACKUP_MEDIUM_TYPE type, PBACKUP_STAT stat);

SLINGA_ERROR Slinga_List(BACKUP_MEDIUM_TYPE type, int use_bup_format, PSAVE_METADATA saves, unsigned int num_saves, unsigned int* saves_found);
SLINGA_ERROR Slinga_Read(BACKUP_MEDIUM_TYPE type, int use_bup_format, char* filename, unsigned char* buffer, unsigned int size, unsigned int* bytes_read);
SLINGA_ERROR Slinga_Write(BACKUP_MEDIUM_TYPE type, int use_bup_format, char* filename, unsigned char* buffer, unsigned int size);
SLINGA_ERROR Slinga_Delete(BACKUP_MEDIUM_TYPE type, char* filename);
SLINGA_ERROR Slinga_Format(BACKUP_MEDIUM_TYPE type);

// need to implment: GetDate(), SetDate() to match BUP library
 
I tweaked the api a bit, nothing major:
C:
// libslinga API
SLINGA_ERROR Slinga_Init(void);
SLINGA_ERROR Slinga_Fini(void);

SLINGA_ERROR Slinga_GetVersion(unsigned char* major, unsigned char* minor, unsigned char* patch);
SLINGA_ERROR Slinga_GetDeviceName(DEVICE_TYPE device_type, char** device_name);

SLINGA_ERROR Slinga_IsPresent(DEVICE_TYPE device_type);
SLINGA_ERROR Slinga_IsReadable(DEVICE_TYPE device_type);
SLINGA_ERROR Slinga_IsWriteable(DEVICE_TYPE device_type);

SLINGA_ERROR Slinga_Stat(DEVICE_TYPE device_type, PBACKUP_STAT stat);
SLINGA_ERROR Slinga_List(DEVICE_TYPE device_type, FLAGS flags, PSAVE_METADATA saves, unsigned int num_saves, unsigned int* saves_found);
SLINGA_ERROR Slinga_Read(DEVICE_TYPE device_type, FLAGS flags, char* filename, unsigned char* buffer, unsigned int size, unsigned int* bytes_read);
SLINGA_ERROR Slinga_Write(DEVICE_TYPE device_type, FLAGS flags, char* filename, unsigned char* buffer, unsigned int size);
SLINGA_ERROR Slinga_Delete(DEVICE_TYPE device_type, char* filename);
SLINGA_ERROR Slinga_Format(DEVICE_TYPE device_type);

// need to implment: GetDate(), SetDate() to match BUP library
// install shim??

- added a Fini() to match Init().
- GetVersion() to get the lib version
- dedicated flags field

I need to think of what time format to use. The internal\cartridge saves use seconds since 1980 stored as an unsigned int. Otherwise I guess I can start porting my Action Replay code (doesn't support writing) and see how that works out.
 
C:
#define TRUE  1
#define FALSE 0
Ick. Are you really intending on supporting compilers so old they offer no boolean types? If you do, choose some other macro names than plain TRUE/FALSE that are almost certain to cause collisions (e.g. SGL defines enums with the same names, and SBL defines macros with the same names).

How is the Slinga_List() function intended to work? I read it as writing up to num_saves entries to the saves array, while returning the total number of saves in saves_found. But wouldn't this require always allocating space for the worst case of 255 metadata entries? How about adding some kind of iteration capability, whether through actual iterators or a block parameter (return the next n entries/return n entries starting from x)?

Make your API const-correct. Apart from general soundness, it works as documentation as you know which pointer arguments are inputs and which are outputs.

There's no explicit create function, and Slinga_Write() only takes a filename parameter - how do the other fields of SAVE_METADATA get set?
 
Thanks antime, this is exactly the type of feedback I was looking for. All of your feedback was 100% spot-on, thank you.

Ick. Are you really intending on supporting compilers so old they offer no boolean types? If you do, choose some other macro names than plain TRUE/FALSE that are almost certain to cause collisions (e.g. SGL defines enums with the same names, and SBL defines macros with the same names).

Removed. I didn't want to include <stdbool.h> (to keep number of includes down). I ended up just tossing the defines as I wasn't using.

How is the Slinga_List() function intended to work? I read it as writing up to num_saves entries to the saves array, while returning the total number of saves in saves_found. But wouldn't this require always allocating space for the worst case of 255 metadata entries? How about adding some kind of iteration capability, whether through actual iterators or a block parameter (return the next n entries/return n entries starting from x)?

I'm envisioning always calling SLINGA_ERROR Slinga_List(DEVICE_TYPE device_type, FLAGS flags, PSAVE_METADATA saves, unsigned int num_saves, unsigned int* saves_found); twice. First time with a 0 num_saves and it will return an buffer error but set saves_found to the correct amount. The user will then have to provide a large enough buffer.

Make your API const-correct. Apart from general soundness, it works as documentation as you know which pointer arguments are inputs and which are outputs.

Agreed, will do. I also starting implementing Doxygen comments for what's it worth.

There's no explicit create function, and Slinga_Write() only takes a filename parameter - how do the other fields of SAVE_METADATA get set?

I've been struggling with this part for a bit. I want to support writing saves to internal\cartridge memory AND arbitrary files for ODEs. Internal\cartridge have no concept of filename (only save_name). I was thinking something like this:

SLINGA_ERROR Slinga_Write(DEVICE_TYPE device_type, FLAGS flags, PSAVE_METADATA save_metadata, unsigned char* buffer, unsigned int size);

With flags set to 0:
- internal\cartridge memory will use the save_name field
- ODEs will use the filename field and auto create and prepend a .BUP file header

With flag DIRECT_WRITE set to 1:
- internal\cartridge memory will still use the save_name field
- ODES will use the filename field but not prepend a .BUP file header

I can also add some logic that if the filename field is NULL, use SAVE_NAME + ".BUP" as the filename. I also plan to add a Slinga_SetMetaData(PSAVE_METADATA metadata, filename, savename, comment, language, date, etc).

Thanks again for your advice, I appreciate it.
 
I need to think of what time format to use. The internal\cartridge saves use seconds since 1980 stored as an unsigned int. Otherwise I guess I can start porting my Action Replay code (doesn't support writing) and see how that works out.

Thanks for making this, I really want to test it - it seems like such a basic function that also is a waste of time to re-invent for every project!

As far as time and date code, it's amazing how a basic part of life is such a giant pain in the ass when it comes to computers and programming. IMO, if there is not already a well established standard epoch for the Saturn - I would just stick to what the standard internal memory uses (1980).

Most systems I've worked with use a epoch of 1970ad (specifically, 0:00:00 UTC, January 1, 1970), but it really doesn't matter as long as it's consistent and people know what the expectation is.

Are people trying to use standard C time libraries? I don't have enough experience with Saturn programming to even know if you can do that. That would be the argument I could see for using the more general standard I mentioned above.
 
Hey @ slinga and fellow forum members, your goals for libslinga are ambitious, and I like how you're addressing multiple backup devices and seamless integration with different Saturn frameworks. Also, the AR Cheat Code functionality sounds like a clever way to shim the BUP library.
One aspect that could benefit from attention is incorporating regression testing software into your development process. As your library continues to evolve, you'll want to ensure that new updates or features don't break existing functionality. Regression testing can automate this for you.
 
Last edited:
Back
Top