Program structure
-----------------
Partition Manager is written to use dynamically loaded libraries, allowing
future filesystems and partition types to be added, without modifying the
existing codebase.

!RunImage imports the following libraries:
  Wimp         - WIMP library
  FS.*         - FileSystem handlers
  Partitions.* - Partition types
  Formats.*    - Partition format identification and initialisation

The calling order of procedures and functions is as follows, where:
  <filesystem> = ADFS, CDFS, SCSIFS, SDFS
  <partition>  = GPT, MBR, FileCore
  <format>     = FAT, FileCore

  PROCread_drives
    FNadd_drives_<filesystem>        - asks every filesystem to add known
                                       drives
    PROCread_partitions
      FNread_partition_table_<filesystem>(<drive>)  - called for every known drive
                                                      decodes the partition table
        FNread_partition_table_<partition>(<drive>) - adds individual partition info
          FNread_bytes_<filesystem>(<drive>, <LBA low>, <LBA high>, <address>, <size in bytes>) - read data from a drive
          PROCunallocated_area(<drive>, <partition>, <LBA low>, <LBA high>, <size in LBA low>, <size in LBA high>) - add an unallocated block
          FNvolume_info_<format>(<drive>, <partition>) - read volume info for the partitions


Assuming there is at least one drive on every filesystem, the procedure /
function call order is as follows:

Function                               Location
--------                               --------
FNinitialise_ADFS                      !PartMgr.FS.ADFS
FNinitialise_CDFS                      !PartMgr.FS.CDFS
FNinitialise_SCSIFS                    !PartMgr.FS.SCSIFS
FNinitialise_SDFS                      !PartMgr.FS.SDFS
PROCread_drives                        !PartMgr.!RunImage
  FNadd_drives_ADFS                    !PartMgr.FS.ADFS
  FNadd_drives_CDFS                    !PartMgr.FS.CDFS
  FNadd_drives_SCSIFS                  !PartMgr.FS.SCSIFS
  FNadd_drives_SDFS                    !PartMgr.FS.SDFS
  PROCread_partitions                  !PartMgr.!RunImage
    FNread_partition_table_ADFS        !PartMgr.FS.ADFS
      FNread_partition_table_GPT       !PartMgr.Partition.GPT
        FNread_bytes_ADFS              !PartMgr.FS.ADFS
        PROCunallocated_area           !PartMgr.!RunImage
        FNvolume_info_FileCore         !PartMgr.Formats.FileCore
      FNread_partition_table_MBR       !PartMgr.Partition.MBR
        FNread_bytes_ADFS              !PartMgr.FS.ADFS
        PROCunallocated_area           !PartMgr.!RunImage
        FNvolume_info_FAT              !PartMgr.Formats.FAT
          FNread_bytes_ADFS            !PartMgr.FS.ADFS
        FNvolume_info_FileCore         !PartMgr.Formats.FileCore
      FNread_partition_table_FileCore  !PartMgr.Partition.FileCore
    FNread_partition_table_CDFS        !PartMgr.FS.CDFS
    FNread_partition_table_SCSIFS      !PartMgr.FS.SCSIFS
      FNread_partition_table_GPT       !PartMgr.Partition.GPT
        FNread_bytes_SCSIFS            !PartMgr.FS.SCSIFS
        PROCunallocated_area           !PartMgr.!RunImage
        FNvolume_info_FileCore         !PartMgr.Formats.FileCore
      FNread_partition_table_MBR       !PartMgr.Partition.MBR
        FNread_bytes_SCSIFS            !PartMgr.FS.SCSIFS
        PROCunallocated_area           !PartMgr.!RunImage
        FNvolume_info_FAT              !PartMgr.Formats.FAT
          FNread_bytes_SCSIFS          !PartMgr.FS.SCSIFS
        FNvolume_info_FileCore         !PartMgr.Formats.FileCore
      FNread_partition_table_FileCore  !PartMgr.Partition.FileCore
    FNread_partition_table_SDFS        !PartMgr.FS.SDFS
      FNread_GUID_GPT                  !PartMgr.Partition.GPT
        FNread_bytes_SDFS              !PartMgr.FS.SDFS
        PROCunallocated_area           !PartMgr.!RunImage
        FNvolume_info_FileCore         !PartMgr.Formats.FileCore
      FNread_partition_table_GPT       !PartMgr.Partition.GPT
      FNread_partition_table_MBR       !PartMgr.Partition.MBR
        FNread_bytes_SDFS              !PartMgr.FS.SDFS
        PROCunallocated_area           !PartMgr.!RunImage
        FNvolume_info_FAT              !PartMgr.Formats.FAT
          FNread_bytes_SDFS            !PartMgr.FS.SDFS
        FNvolume_info_FileCore         !PartMgr.Formats.FileCore
      FNread_partition_table_FileCore  !PartMgr.Partition.FileCore
FNfinalise_ADFS                        !PartMgr.FS.ADFS
FNfinalise_CDFS                        !PartMgr.FS.CDFS
FNfinalise_SCSIFS                      !PartMgr.FS.SCSIFS
FNfinalise_SDFS                        !PartMgr.FS.SDFS



Global functions
----------------
The following global functions are available for general use:

PROCadd64(RETURN <low A>, RETURN <high A>, <low B>, <high B>)
                  - 64bit add.  A=A+B
FNcrc32(<address>, <size>, <seed>)
                  - returns the CRC for a block of memory, using a starter seed
PROCerror_fatal(<error no>, <text>)
                  - reports a fatal error and exits Partition Manager
FNerror_recover(<error no>, <text>)
                  - reports an error and returns one of the following:
                     SUCCESS% - if <error no>=-1 on entry (ie when using EVAL
                                to call a function in place of <error no>)
                     RETRY%   - Retry button pressed
                     SKIP%    - Skip button pressed
                     CANCEL%  - Cancel button pressed
FNdiscop(<drive>, <action>, <LBA low>, <LBA high>, <buffer>, <size>)
                  - will perform the approriate FileCore_DiscOp64/SectorDiscOp/
                    DiscOp for the drive and return TRUE if successful
FNfatal(<internal error number>)
                  - will return the text for an internal error
FNfilecore_inaccessible(<drive>, <partition>)
                  - returns TRUE if a partition is not fully addressible by
                    FileCore and will call PROCpartition_inaccessible if
                    required
FNGPT_MBR_FileCore(<drive>)
                  - checks <drive> for GPT, then MBR, falling back to
                    FileCore if neither partition type are found
PROCpartition_inaccessible(<drive>, <partition>)
                  - will mark the partition as inaccessible by FileCore
FNlcase(<string>) - returns <string> in lowercase
FNlog2(<number>)  - returns the number of bits required for <number>
FNmodule_version(<string>)
                  - returns a Module version * 100.  ie 1.00 will return 100
FNstr(<address>, <max length>)
                  - returns the string at <address>, terminated by char<32
FNstr_utf(<address>, <max length>)
                  - reads the UTF string at <address>, terminated by char<32,
                    returns a regular string
FNtrim(<string>)  - returns a string with trailing spaces removed
PROCunallocated_area(<drive>, <partition>, <start LBA low>, <start LBA high>, <size in LBA low>, <size in LBA high>)
                  - adds an unallocated partition area to the Partition_Info%()
                    and Partition_Text$() arrays
FNvalid_range(<drive>, <start LBA low>, <start LBA high>, <end LBA low>, (end LBA high>)
                  - returns TRUE if the start/end LBA pair are within the
                    drive size
FNword(<address>) - return a 16bit little-endian word
PROCword_write(<address>, <value>)
                  - write a 16bit little-endian word
PROCzero_block(<address>, <size>)
                  - zero's a block of memory.  Must be a word aligned address
                    and size



Global variables
----------------
The following variables should be updated as appropriate:

data_limit%       - size of the data% memory block for general drive data reads
Drive_Count%      - Drive count, increased by FNadd_drives_* whenever a drive
                    is added to Drive_Info%()
OS_Version%       - OS version * 100.  eg 311 for RISC OS 3.11
Partition_Count%  - Total count of partitions.  Should be increased by
                    FNread_partition_table_* whenever a partition is added to
                    the Partition_Info%() and Partition_Text$() arrays
SUCCESS%          - value returned by FNerror_recover
RETRY%            - value returned by FNerror_recover
SKIP%             - value returned by FNerror_recover
CANCEL%           - value returned by FNerror_recover



Global drive arrays
-------------------
The following arrays should be updated by the relevant functions:

Drive_Info%(<n>, <variable>) where <variable> is,
  D_Drive_No%         - Drive number as used by the filesystem its on
  D_LBA_Alignment%    - size of physical sectors if different from the LBA size
  D_LBA_Size%         - size of LBA in bytes
  D_LBA_Log2%         - LOG2(LBA)
  D_Partitions%       - Number of partitions on the drive
  D_Max_Partitions    - Max number of partitions supported
  D_Read_Only%        - TRUE if drive is read-only
  D_Removable%        - <>0 if drive is removable
  D_Size_In_LBA_Low%  - Low dword of drive size in LBA
  D_Size_In_LBA_High% - High dword of drive size in LBA

Entries are created by FNadd_drives_* functions, in the form:
  Drive_Info%(Drive_Count%, <variable>)

Drive_Text$(<n>, <variable>) where <variable> is,
  D_Icon%             - Icon name for drive.  eg "hd", "cd", "sd"
  D_GUID%             - Drive unique identifier
  D_Model%            - Drive Model (if known)
  D_Partitioning%     - Partitioning type.  eg "GPT", "MBR", "FileCore"
  D_Physical_Name%    - Name of the physical drive presented to the user. eg
                          ADFS 0:1
  D_Serial%           - Drive Serial number (if known)
  FS_Library%         - FileSystem library that added the drive.  eg "ADFS"

Entries are created by FNadd_drives_* functions, in the form:
  Drive_Text%(Drive_Count%, <variable>)

Drive_Count% should be increased when a drive is added to these arrays



Global partition arrays
-----------------------
The following arrays should be updated by the relevant functions:

Partition_Info%(<drive>, <partition>, <variable>) where <variable> is,
  P_Bootable%            - TRUE if the drive is a boot drive
  P_Colour%              - Wimp colour of the partition
  P_Display%             - TRUE if the partition should be shown in the
                           volume table
  P_End_CHS_Cylinder%    - (MBR only) end cylinder
  P_End_CHS_Head%        - (MBR only) end head
  P_End_CHS_Sector%      - (MBR only) end sector
  P_Free_Low%            - Low dword of Free bytes
  P_Free_High%           - High dword of Free bytes
  P_OS_Size_In_LBA_Low%  - Low dword of FileCore reported drive size in LBA
  P_OS_Size_In_LBA_High% - High dword of FileCore reported drive size in LBA
  P_Size_In_LBA_Low%     - Low dword of partition size in LBA
  P_Size_In_LBA_High%    - High dword of partition size in LBA
  P_Start_CHS_Cylinder%  - (MBR only) start cylinder
  P_Start_CHS_Head%      - (MBR only) start head
  P_Start_CHS_Sector%    - (MBR only) start sector
  P_Start_LBA_Low%       - Low dword of start LBA
  P_Start_LBA_High%      - High dword of start LBA
  P_Type%                - (MBR, CDFS only) partition type

Entries are created by FNread_partition_table_* functions, in the form:
  Partition_Info%(<drive>, <partition>, <variable>)

Partition_Text$(<drive>, <partition>, <variable>) where <variable> is,
  P_Additional_Info%     - Additional text to append to the Name in the
                           volume table
  P_Format%              - Format of partition.  eg "FileCore", "FAT"
  P_GUID%                - Global Unique Identifier for the partition
  P_Layout%              - Layout of partition.  eg "E+, Boot, Big map"
  P_Logical_Drive%       - Logical drive OS is using.  eg "ADFS::4"
  P_Volume_Name%         - Volume label of partition.  eg "HardDisc4"

Entries are created by FNread_partition_table_* functions, in the form:
  Partition_Info%(<drive>, <partition>, <variable>)




Global errors
-------------
These variable should be passed to FNfatal(<variable>) to report a fatal error:

Buffer_Overflow% - "LBA size too large for buffer"
                   Indicates a DiscOp would exceed the data% buffer



Implementing a <filesystem>
---------------------------
Filesystem drivers placed in the FS subfolder should implement the following
functions:

FNinitialise_<filesystem>
  Initialise any variables or code required later.  This is only called once
  when the program loads, so should not contain anything drive specific

  Return value: =TRUE if filesytem exists
                =FALSE if filesystem does not exist


FNfinalise_<filesystem>
  Called just before the program exits, allowing filesystems to shutdown
  cleanly, releasing RMA claims or devices etc

  Return value: =0


FNadd_drives_<filesystem>
  Add physical drives on the filesystem to Drive_Info%()

  Return value: =0


FNread_partition_table_<filesystem>(<drive>)
  Adds partitions accessible through the filesystem.  If the filesystem
  supports GPT and MBR partitioning, the call should be passed on to the
  relevent partition function.  eg:

    REM Check for GPT partition
    IF NOT FNread_partition_table_GPT(<drive>) THEN
      REM If that failed, check for MBR partitions
      IF NOT FNread_partition_table_MBR(<drive>) THEN
        REM If that failed, check for a full disc FileCore partition
        =FNread_partition_table_FileCore(<drive>)
      ENDIF
    ENDIF

  Return value: =0


FNread_bytes_<filesystem>(<drive>, <LBA low>, <LBA high>, <buffer>, <size>)
  This should reference Drive_Info%(<drive>, <variable>) for the physical
  drive info and read the requested LBA(s) into <buffer>

  Return value: =TRUE if the read was successful
                =FALSE if an error occurred


FNwrite_bytes_<filesystem>(<drive>, <LBA low>, <LBA high>, <buffer>, <size>)
  This should reference Drive_Info%(<drive>, <variable>) for the physical
  drive info and write <buffer> to the disc

  Return value: =TRUE if the write was successful
                =FALSE if an error occurred


FNattach_<filesystem>(<drive>)
  This should reference Drive_Info%(<drive>, <variable>) for the physical
  drive info and perform a detach/attach



Implementing a <format>
-----------------------
Files placed in the "Formats" subfolder should include the following
functions:

FNinitialise_<format>
  Setup any global variables or code required later


FNformats_<format>(RETURN A$())
  Add the list off supported layouts to A$() and return the number of
  entries in the array-1.  eg

    DEF FNformats_FAT(RETURN A$())
      LOCAL ERROR
      ON ERROR LOCAL =2
      IF DIM(A$()) THEN A$()="Default","FAT16","FAT32"
    =2


FNvolume_info_<format>(<drive>, <partition>)
  Should query a partition, fill in the appropriate field in the
  partition array and return TRUE if recognised.

  Partition and Drive info can be read from the relevent arrays:
  Drive_Text$(<drive>, <variable>)
  Drive_Info$(<drive>, <variable>)
  Partition_Text$(<drive>, <partition>, <variable>)
  Partition_Info%(<drive>, <partition>, <variable>)

  The partition LBA start/end can be derived from:
  Partition_Info%(<drive>, <partition>, P_Start_LBA_Low%)
  Partition_Info%(<drive>, <partition>, P_Start_LBA_High%)
  Partition_Info%(<drive>, <partition>, P_Size_In_LBA_Low%)
  Partition_Info%(<drive>, <partition>, P_Size_In_LBA_High%)

  To read from the drive use FNread_bytes_<filesystem>.  eg. Reading the
  first LBA of the partition to buffer% could be achieved with the following
  example:

  IF NOT EVAL("FNread_bytes_"+Drive_Text$(<drive>, FS_Library%)+"("+STR$(<drive>)+",&"+STR$~Partition_Info%(<drive>, <partition>, P_Start_LBA_Low%)+",&"+STR$~Partition_Info%(<drive>, <partition>, P_Start_LBA_High%)+",&"+STR$~buffer%+","+Drive_Info%(<drive>, D_LBA_Size%)+")") THEN
    PROCpartition_inaccessible(<drive>, <partition>)
    =TRUE
  ENDIF


FNvolume_init_<format>(<drive>, <partition>, <allocation size>, <layout>)
  Lay down the volume structure within the partition, where:

    <allocation size> is the requested allocation size.  This should be
                      be used as the starting size and increased if required
    <layout>          the requested layout in string form.  eg "E" or "E+"

  The following Partition_Text$() variables are set before called and may be
  used while laying down the structure:

     P_Format%          
     P_Volume_Name%
     P_Bootable%

  The partition LBA start/end can be derived the following Partition_Info%()
  variables:

    P_Start_LBA_Low%    - start LBA lower 32 bits
    P_Start_LBA_High%   - start LBA upper 32 bits
    P_Size_In_LBA_Low%  - size in LBA lower 32 bits
    P_Size_In_LBA_High% - size in LBA upper 32 bits

  The volume area can assumed to be formatted.
  FNwrite_bytes_<filesystem>() should be used to write to the partition in
  conjuction with FNerror_recover() to prompt when a write error occurs.  eg
    where,
             D% = <drive>
       LBA_low% = lower 32 bits of the LBA
      LBA_high% = upper 32 bits of the LBA
        buffer% = pointer to block to write
          size% = size of block to write

    LOCAL E%
    REPEAT
      E%=FNerror_recover(EVAL("FNwrite_bytes_"+Drive_Text$(D%, FS_Library%)+"("+STR$D%+",&"+STR$~LBA_low%+",&"+STR$~LBA_high%+",&"+STR$~buffer%+",&"+STR$~size%+")"), "Write error laying down volume structure")
    UNTIL E%=SUCCESS% OR E%=SKIP% OR E%=CANCEL%
    IF E%=CANCEL% THEN <cancel action>



Implementing a <partition>
--------------------------
Files placed in the "Partition" subfolder are called by filesystem
FNread_partition_table_* functions that support the partitioning it decodes.


A <partition> file contains the following functions, where <filename> is the
filename of itself:

FNinitialise_<filename>
  Initialise any global variables or code required later

FNread_partition_table_<filename>(<drive>)
  This should attempt to decode the partition table on the disc, returning
  TRUE if it can decode the partition table and FALSE if it's not recognised.
  To read from the disc FNread_bytes_* should be called in the following manor:

    IF NOT EVAL("FNread_bytes_"+Drive_Text$(<drive>, FS_Library%) + "(" + STR$<drive>+",0,0,&"+STR$~data%+",&200)") THEN =FALSE

  This will attempt to read 512 bytes from LBA 0 and exit early if the read
  fails.

  As partitions are decoded, they should be added to the following arrays,
  where:

    <drive> is the drive index passed on entry
    <n> is the partition number
 
    Partition_Info%(<drive>, <n>, <variable>)
    Partition_Text$(<drive>, <n>, <variable>)

  When a partition is added to the array, the following variables should be
  increased:

    Drive_Info%(<drive>, D_Partitions%)
    Partition_Count%


FNwrite_partition_table_<filename>(<drive>)
  This should write an the partition table created from the partition arrays.
  The number of partitions can be obtained from:

    Drive_Info%(<drive>, D_Partitions%)

  Partition info can be obtained from:

    Partition_Info%(<drive>, <n>, <variable>)
    Partition_Text$(<drive>, <n>, <variable>)

  The following Partition_Info%() variables can be used to build the partition
  table:

    P_Start_LBA_Low%    - start LBA lower 32 bits
    P_Start_LBA_High%   - start LBA upper 32 bits
    P_Size_In_LBA_Low%  - size in LBA lower 32 bits
    P_Size_In_LBA_High% - size in LBA upper 32 bits

  The following Partition_Text$() variables can be used to build the partition
  table:

    P_Format% - FAT / FileCore.  A format of "-" should not be skipped
                as it's an unallocated area on the disc
    P_GUID$   - The unique identifier for this partition.  If blank, generate
                a new GUID otherwise write this value into the partition GUID

  FNwrite_bytes_<filesystem>() should be used to write to the partition in
  conjuction with FNerror_recover() to prompt when a write error occurs.  eg
    where,
             D% = <drive>
       LBA_low% = lower 32 bits of the LBA
      LBA_high% = upper 32 bits of the LBA
        buffer% = pointer to block to write
          size% = size of block to write

    LOCAL E%
    REPEAT
      E%=FNerror_recover(EVAL("FNwrite_bytes_"+Drive_Text$(D%, FS_Library%)+"("+STR$D%+",&"+STR$~LBA_low%+",&"+STR$~LBA_high%+",&"+STR$~buffer%+",&"+STR$~size%+")"), "Failed to write the partition table")
    UNTIL E%=SUCCESS% OR E%=SKIP% OR E%=CANCEL%
    IF E%=CANCEL% THEN <cancel action>
