// Hacked AHI driver based on the MNT ZZ9000AX Amiga AHI Driver
#include <exec/types.h>
#include <exec/exec.h>
#include <exec/memory.h>

#include <dos/dos.h>
#include <dos/dostags.h>
#include <exec/interrupts.h>
#include <hardware/intbits.h>

#include <devices/trackdisk.h>
#include <hardware/dmabits.h>
#include <hardware/cia.h>
#include <exec/tasks.h>
#include <dos/dosextens.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/utility.h>
#include <proto/expansion.h>
#include <clib/debug_protos.h>
#include "floppyaudio.h"
#include "ahi_sub.h"
#include "ahi_sub_protos.h"

#include <math.h>
#include <stdint.h>

#define STR(s) #s
#define XSTR(s) STR(s)

#define DEVICE_NAME "floppyaudio.audio"
#define DEVICE_DATE "(27.03.2026)"
#define DEVICE_VERSION 4
#define DEVICE_REVISION 1
#define DEVICE_ID_STRING "FloppyAudio " XSTR(DEVICE_VERSION) "." XSTR(DEVICE_REVISION) " " DEVICE_DATE
#define DEVICE_PRIORITY 0

struct ExecBase     *SysBase;
struct UtilityBase  *UtilityBase;
struct Library      *AHIsubBase  = NULL;
struct DosLibrary   *DOSBase     = NULL;
struct DriverBase   *driverBase;


int __attribute__((no_reorder)) _start() { return -1; }

asm("romtag:                                \n"
    "       dc.w    "XSTR(RTC_MATCHWORD)"   \n"
    "       dc.l    romtag                  \n"
    "       dc.l    endcode                 \n"
    "       dc.b    "XSTR(RTF_AUTOINIT)"    \n"
    "       dc.b    "XSTR(DEVICE_VERSION)"  \n"
    "       dc.b    "XSTR(NT_LIBRARY)"      \n"
    "       dc.b    "XSTR(DEVICE_PRIORITY)" \n"
    "       dc.l    _device_name            \n"
    "       dc.l    _device_id_string       \n"
    "       dc.l    _auto_init_tables       \n"
    "endcode:                               \n");

const char device_name[] = DEVICE_NAME;
const char device_id_string[] = DEVICE_ID_STRING;



static uint32_t __attribute__((used)) init(BPTR seg_list asm("a0"), struct Library *dev asm("d0")) {
    SysBase = *(struct ExecBase **)4L;
    if (!(DOSBase = (struct DosLibrary *)OpenLibrary((STRPTR)"dos.library",0))) return 0;
    if (!(UtilityBase = (struct UtilityBase *)OpenLibrary((STRPTR)"utility.library",0))) return 0;

    // As we're assuming its always going to be DF1, we'll just check if that device exists
    struct MsgPort* drivePort = CreateMsgPort();
    if (!drivePort) return FALSE;

    struct IOExtTD* io = (struct IOExtTD *)CreateIORequest(drivePort, sizeof(struct IOExtTD));
    if (!io) {
        DeleteMsgPort(drivePort);
        return FALSE;
    }

    BOOL ret = OpenDevice((CONST_STRPTR)TD_NAME, 1, (struct IORequest *)io, 0) == 0;
    if (ret) CloseDevice((struct IORequest *)io);
    DeleteIORequest(io);
    DeleteMsgPort(drivePort);

    return ret ? (uint32_t)dev : 0;
}

static uint8_t* __attribute__((used)) expunge(struct Library *libbase asm("a6")) {
    if(DOSBase)       { CloseLibrary((struct Library *)DOSBase); DOSBase = NULL; }
    if(UtilityBase)   { CloseLibrary((struct Library *)UtilityBase); UtilityBase = NULL; }
    return 0;
}

static uint8_t __attribute__((used)) null() {
    return 0;
}

static void __attribute__((used)) open(struct Library *dev asm("a6"), struct IORequest *iotd asm("a1"), uint32_t num asm("d0"), uint32_t flags asm("d1")) {
    iotd->io_Error = 0;
    dev->lib_OpenCnt++;
}

static uint8_t* __attribute__((used)) close(struct Library *dev asm("a6"), struct IORequest *iotd asm("a1")) {
    return 0;
}

static void __attribute__((used)) begin_io(struct Library *dev asm("a6"), struct IORequest *io asm("a1")) {
    if (io == NULL) return;
    if (!(io->io_Flags & IOF_QUICK)) ReplyMsg(&io->io_Message);
}

static uint32_t __attribute__((used)) abort_io(struct Library *dev asm("a6"), struct IORequest *io asm("a1")) {
    if (!io) return IOERR_NOCMD;
    io->io_Error = IOERR_ABORTED;

    return IOERR_ABORTED;
}

static uint32_t __attribute__((used)) SoundFunc(struct Hook *hook asm("a0"), struct AHIAudioCtrlDrv *actrl asm("a2"), struct AHISoundMessage *chan asm("a1")) {
    return 0;
}

// cleanup!
static void MixerCleanup(struct FloppyData* floppyData, struct AHIAudioCtrlDrv* AudioCtrl, struct ExecBase* SysBase) {
    // Stop Paula DMA
    DSKLEN = 0x4000;                // Reset DMA
    DMACON = DMAF_DISK;             // Clear disk DMA enable    
    Delay(1);

    // De-Select DF1
    CIABPRB = 0xFF;
    Delay(1);
    CIABPRB = floppyData->oldCIA;   // Restore CIABPRB after we messed with it

    // Close all drives except DF1
    // Free some IO
    for (UWORD i=0; i<4; i++) {
        if (floppyData->drives[i].isOpen) {
            floppyData->drives[i].io->iotd_Req.io_Command = CMD_START;
            DoIO((struct IORequest *)floppyData->drives[i].io);
            CloseDevice((struct IORequest *)floppyData->drives[i].io);
            floppyData->drives[i].isOpen = FALSE;
        }
        if (floppyData->drives[i].io) {
            DeleteIORequest(floppyData->drives[i].io);
            floppyData->drives[i].io = NULL;
        }
        if (floppyData->drives[i].port) {
            DeleteMsgPort(floppyData->drives[i].port);
            floppyData->drives[i].port = NULL;
        }
    }   

    if (floppyData->mixerAbort != -1) {
        FreeSignal( floppyData->mixerAbort );
        floppyData->mixerAbort = -1;
    }

    Forbid();
    Signal((struct Task*)floppyData->mainTask, 1L << floppyData->mixerReady);
    floppyData->mixerTask = NULL;    
}


void Mixer() {
    SysBase = *((struct ExecBase**)4);
    struct Process* proc = (struct Process *)FindTask(NULL);
    struct FloppyData* floppyData = (struct FloppyData*)proc->pr_Task.tc_UserData;
    struct AHIAudioCtrlDrv* AudioCtrl = floppyData->audioCtrl;

    floppyData->oldCIA = CIABPRB; 

    floppyData->mixerAbort = AllocSignal( -1 );
    if(floppyData->mixerAbort==-1) {
        MixerCleanup(floppyData, AudioCtrl, SysBase);
        return;
    }

    // Signal the main thing we're ready
    Signal((struct Task*) floppyData->mainTask, 1L << floppyData->mixerReady);

    // Lock out all floppy drives so they can't screw with disk dma without worrying
    for (UWORD i=0; i<4; i++) {
        floppyData->drives[i].port = CreateMsgPort();  
        if (!floppyData->drives[i].port) {
            MixerCleanup(floppyData, AudioCtrl, SysBase);
            return;
        }
        floppyData->drives[i].io = (struct IOExtTD*)CreateIORequest(floppyData->drives[i].port, sizeof(struct IOExtTD));
        if (!floppyData->drives[i].io) {
            MixerCleanup(floppyData, AudioCtrl, SysBase);
            return;
        }
        floppyData->drives[i].isOpen = OpenDevice((CONST_STRPTR)TD_NAME, i, (struct IORequest *)floppyData->drives[i].io, 0) == 0;
        if (floppyData->drives[i].isOpen) {
            floppyData->drives[i].io->iotd_Req.io_Command = CMD_STOP;
            DoIO((struct IORequest *)floppyData->drives[i].io);
        }
    }

    floppyData->oldCIA = CIABPRB; 

    // Use hardware directly to select DF1
    CIABPRB = 0xFF;
    Delay(1);

    // Enable motor (needed only for winuae to show writing is happening)
    UBYTE CIAValue = 0xFF ^ CIAF_DSKMOTOR;
    CIABPRB = CIAValue;
    Delay(1);

    // select DF1
    CIAValue&= ~CIAF_DSKSEL1;
    CIABPRB = CIAValue;
    Delay(1);
     
    // Upper side means stereo
    if (floppyData->audioid == FLOPPYAUDIO_ID_STEREO) {
        CIAValue&= ~CIAF_DSKSIDE;
        CIABPRB = CIAValue;
        Delay(1);
    }

    // Mixing
    floppyData->bufferPlaying = 0;
    floppyData->bufferWritePosition = floppyData->dmaBuffers[1]; floppyData->bufferWritePosition += 2;
    floppyData->bufferWordsLeft = DMA_BUF_WORDS-2;     
    BOOL firstRun = TRUE;

    // Prepare disk dma
    Forbid();
    DSKLEN = 0x4000;
    DMACON = DMAF_SETCLR | DMAF_MASTER | DMAF_DISK;
    DSKLEN = 0x4000;
    Permit();
    WORD* src;

    struct MsgPort*     timerPort =  CreateMsgPort();
    struct timerequest* timerIO = (struct timerequest*)CreateIORequest(timerPort, sizeof(struct timerequest));
    OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest*)timerIO, 0);

    for (;;) {
        ULONG signals = SetSignal(0L,0L);
        if (signals & (SIGBREAKF_CTRL_C | (1L<<floppyData->mixerAbort))) break;

        if (floppyData->playing) {
            timerIO->tr_node.io_Command = TR_ADDREQUEST;
            timerIO->tr_time.tv_secs    = 0;
            ULONG playerFreq = AudioCtrl->ahiac_PlayerFreq >> 16;
            if (playerFreq > 0) {
                ULONG microSeconds = 1000000 / playerFreq;
                timerIO->tr_time.tv_micro = (microSeconds * 3) / 4;
            } else timerIO->tr_time.tv_micro   = 15000;  // should be 20ms, but allow a little overhead
            DoIO((struct IORequest*)timerIO);

            CallHookPkt( AudioCtrl->ahiac_PlayerFunc, AudioCtrl, NULL );

            if (!(*AudioCtrl->ahiac_PreTimer)()) {

                if (playerFreq > 0) {
                    AudioCtrl->ahiac_BuffSamples = (AudioCtrl->ahiac_MixFreq + playerFreq -1 ) / playerFreq;
                }
                else
                    AudioCtrl->ahiac_BuffSamples = 624;
                CallHookPkt( AudioCtrl->ahiac_MixerFunc, AudioCtrl, floppyData->mixBuffer );

                // We will *always* run behind playback because theres a small overhead with the way the floppy drive works, so we dont care if the end of the buffer gets truncated.
                UWORD samplesLeft = (UWORD)AudioCtrl->ahiac_BuffSamples;
                UWORD* inBuffer = (UWORD*)floppyData->mixBuffer;            
                while (samplesLeft) {        
                    UWORD samplesToCopy = samplesLeft > floppyData->bufferWordsLeft ? floppyData->bufferWordsLeft : samplesLeft;
                    samplesLeft -= samplesToCopy;
                    floppyData->bufferWordsLeft -= samplesToCopy;
                    
                    // Copy the samples to the dma buffer
                    switch (floppyData->audioid) {
                        case FLOPPYAUDIO_ID_MONO:
                            CopyMem(inBuffer, floppyData->bufferWritePosition, samplesToCopy<<1);
                            floppyData->bufferWritePosition += samplesToCopy;
                            inBuffer += samplesToCopy;
                            break;
                        case FLOPPYAUDIO_ID_STEREO:
                            src = (WORD*)inBuffer;
                            while (samplesToCopy--) {
                                UWORD l = (UWORD)*src++ & 0xFF00;
                                UWORD r = (UWORD)*src++ >> 8;
                                *floppyData->bufferWritePosition++ = l | r;
                            }                          
                            inBuffer = (UWORD*)src;
                            break;
                        default:
                            samplesLeft = 0;  // shouldn't get here
                            break;
                    } 
                    
                    // Ran out of space in DMA buffer         
                    if (floppyData->bufferWordsLeft == 0) {
                        // Start disk dma writing
                        
                        ULONG dskptr = (ULONG)floppyData->dmaBuffers[1^floppyData->bufferPlaying];
                        UWORD dsklen = DMA_BUF_WORDS | 0xC000;
                        floppyData->dmaBuffers[1^floppyData->bufferPlaying][0] = 0x0001;   // force sync
                        floppyData->dmaBuffers[1^floppyData->bufferPlaying][1] = 0x0001;   // force sync

                        if (!firstRun) {                                      
                            Forbid();  // Yeah ok, this is VERY naughty tut tut tut
                            while (!(INTREQR & INTF_DSKBLK)) {};
                            INTREQ = INTF_DSKBLK;
                            DSKPTH = (UWORD)(dskptr >> 16);
                            DSKPTL = (UWORD)(dskptr & 0xFFFF);
                            DSKLEN = dsklen;
                            DSKLEN = dsklen;
                            Permit();
                        } else {
                            firstRun = FALSE;                     
                            DSKPTH = (UWORD)(dskptr >> 16);
                            DSKPTL = (UWORD)(dskptr & 0xFFFF);
                            DSKLEN = dsklen;
                            DSKLEN = dsklen;
                        }

                        // Switch buffers
                        floppyData->bufferWritePosition = &floppyData->dmaBuffers[floppyData->bufferPlaying][2];
                        floppyData->bufferPlaying ^= 1;
                        floppyData->bufferWordsLeft = DMA_BUF_WORDS-2; 
                    }
                }

                (*AudioCtrl->ahiac_PostTimer)();

                // Try to keep the player in sync
                WaitIO((struct IORequest*)timerIO);               
            } else Delay(1); 
        } else Delay(1);                    
    }

    AbortIO((struct IORequest*)timerIO);
    WaitIO((struct IORequest*)timerIO);
    CloseDevice((struct IORequest*)timerIO);
    DeleteIORequest(timerIO);
    DeleteMsgPort(timerPort);

    MixerCleanup( floppyData, AudioCtrl, SysBase );
}

static uint32_t __attribute__((used)) intAHIsub_AllocAudio(struct TagItem *tagList asm("a1"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
    ULONG audioid = tagList ? GetTagData( AHIDB_AudioID, FLOPPYAUDIO_ID_MONO, tagList ) : FLOPPYAUDIO_ID_MONO;
    struct FloppyData* floppyData = (struct FloppyData*)AllocVec( sizeof(struct FloppyData), MEMF_CLEAR | MEMF_PUBLIC );
    if( floppyData == NULL ) return AHISF_ERROR;

    // Force our mixing rate
    AudioCtrl->ahiac_DriverData = floppyData;
    AudioCtrl->ahiac_MixFreq = FLOPPYAUDIO_SAMPLE_RATE;
    floppyData->audioid      = audioid;
    floppyData->mixerReady   = AllocSignal( -1 );
    floppyData->mainTask     = (struct Process*) FindTask( NULL );
    floppyData->mixerAbort   = -1;
    floppyData->playing = FALSE;

    struct TagItem proctags[] = {
        { NP_Entry,    (ULONG) Mixer       },
        { NP_Name,     (ULONG) device_name },
        { NP_Priority, 10                  },  
        { TAG_DONE,    0                   }
    };
    
    // Mixing buffer for AHI (it'll be 8-Bit Stereo or 16-bit mono)
    floppyData->mixBuffer = AllocVec( 65536 * 4UL, MEMF_ANY | MEMF_PUBLIC );
    floppyData->audioCtrl = AudioCtrl;
    if(!floppyData->mixBuffer) {
        return AHIE_NOMEM;
    }

    // DMA buffers in CHIP
    for (UWORD i=0; i<NUM_BUFFERS; i++) {
        floppyData->dmaBuffers[i] = (UWORD*)AllocMem( DMA_BUF_BYTES, MEMF_CHIP | MEMF_CLEAR );
        if (!floppyData->dmaBuffers[i]) {
            return AHIE_NOMEM;
        }
    }
    // Start main mixer
    Forbid();
    floppyData->mixerTask = CreateNewProc( proctags );
    if (floppyData->mixerTask) floppyData->mixerTask->pr_Task.tc_UserData = floppyData;
    Permit();
    if (!floppyData->mixerTask) return AHIE_NOMEM;
    // Wait for mixer to signal it is alive
    Wait(1L << floppyData->mixerReady);
    // Check if mixer closed instantly
    if(!floppyData->mixerTask) return AHIE_UNKNOWN;

    // And return what we will allow
    return AHISF_MIXING | (audioid == FLOPPYAUDIO_ID_STEREO ? AHISF_KNOWSTEREO : 0);
}

static void __attribute__((used)) intAHIsub_FreeAudio(struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
    if (AudioCtrl->ahiac_DriverData) {
        struct FloppyData* floppyData = (struct FloppyData*)AudioCtrl->ahiac_DriverData;

        if (floppyData->mixerTask) {                    
            if (floppyData->mixerAbort!=-1) Signal((struct Task*)floppyData->mixerTask, 1L << floppyData->mixerAbort);
            // Wait for it to close
            Wait(1L << floppyData->mixerReady);
            floppyData->mixerTask = NULL;
        }
        if (floppyData->mixerReady!=-1) FreeSignal(floppyData->mixerReady);

        // Free mixing buffer
        if (floppyData->mixBuffer) {
            FreeVec(floppyData->mixBuffer);
            floppyData->mixBuffer = NULL;            
        }

        // Free chip buffers
        for (UWORD i=0; i<NUM_BUFFERS; i++) 
            if (floppyData->dmaBuffers[i]) {
                FreeMem(floppyData->dmaBuffers[i], DMA_BUF_BYTES);
                floppyData->dmaBuffers[i] = NULL;
            }        

        FreeVec(AudioCtrl->ahiac_DriverData);
        AudioCtrl->ahiac_DriverData = NULL;

    }
}

static void __attribute__((used)) intAHIsub_Stop(uint32_t Flags asm("d0"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
    if( Flags & AHISF_PLAY ) {
        struct FloppyData* floppyData = (struct FloppyData*)AudioCtrl->ahiac_DriverData;
        if (!floppyData) {
            return ;
        }
        floppyData->playing = FALSE;
    }    
}

static uint32_t __attribute__((used)) intAHIsub_Start(uint32_t flags asm("d0"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
    intAHIsub_Stop( flags, AudioCtrl );
    // Block record
    if (flags & AHISF_RECORD) return AHIE_UNKNOWN;

    // Play only
    if (flags & AHISF_PLAY) {
        struct FloppyData* floppyData = (struct FloppyData*)AudioCtrl->ahiac_DriverData;
        if (!floppyData) {
            return AHIE_UNKNOWN;
        }
        floppyData->playing = TRUE;
        return AHIE_OK;
    }

    return AHIE_UNKNOWN;
}

void __attribute__((used)) cintAHIsub_Enable(struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
   // Permit(); // lazy
}

void __attribute__((used)) cintAHIsub_Disable(struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
   // Forbid();  // lazy
}

static int32_t __attribute__((used)) intAHIsub_GetAttr(uint32_t attr_ asm("d0"), int32_t arg_ asm("d1"), int32_t def_ asm("d2"), struct TagItem *tagList asm("a1"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
    uint32_t attr = attr_;
    int32_t arg = arg_, def = def_;


    switch(attr)    {
        case AHIDB_Bits: return 16; 
        case AHIDB_Frequencies: return 1;
        case AHIDB_Frequency: return FLOPPYAUDIO_SAMPLE_RATE;
        case AHIDB_Index: return 0;
        case AHIDB_Author: return (int32_t) "RobSmithDev";
        case AHIDB_Copyright: return (int32_t) "Public Domain";
        case AHIDB_Version: return (int32_t) device_id_string;
        case AHIDB_Annotation: return (int32_t) "https://robsmithdev.co.uk";
        case AHIDB_Record: return FALSE;
        case AHIDB_FullDuplex: return FALSE;
        case AHIDB_Realtime: return TRUE;
        case AHIDB_MaxChannels: {
            ULONG id = tagList ? GetTagData( AHIDB_AudioID, FLOPPYAUDIO_ID_MONO, tagList ) : FLOPPYAUDIO_ID_MONO;
            return ( id == FLOPPYAUDIO_ID_STEREO ) ? 2 : 1;    
        }
        case AHIDB_Outputs: return 1;
        case AHIDB_Output:return (int32_t)"DF1:DAC/PICO";
        default: return def;
    }
}

static int32_t __attribute__((used)) intAHIsub_HardwareControl(uint32_t attr asm("d0"), uint32_t arg asm("d1"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
    return 0;
}

static uint32_t __attribute__((used)) intAHIsub_SetEffect(uint8_t *effect asm("a0"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
    return AHIS_UNKNOWN;
}

static uint32_t __attribute__((used)) intAHIsub_LoadSound(uint16_t sound asm("d0"), uint32_t type asm("d1"), struct AHISampleInfo *info asm("a0"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
    return AHIS_UNKNOWN;
}

static uint32_t __attribute__((used)) intAHIsub_UnloadSound(uint16_t sound asm("d0"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2")) {
    return AHIS_UNKNOWN;
}

static void __attribute__((used)) intAHIsub_Update(uint32_t flags asm("d0"), struct AHIAudioCtrlDrv *AudioCtrlDrv asm("a2")) {
}

static uint32_t __attribute__((used)) intAHIsub_SetVol(uint16_t channel asm("d0"), uint32_t volume asm("d1"), uint32_t pan asm("d2"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2"), uint32_t flags asm("d3")) {
    return AHIS_UNKNOWN;
}

static uint32_t __attribute__((used)) intAHIsub_SetFreq(uint16_t channel asm("d0"), uint32_t freq asm("d1"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2"), uint32_t flags asm("d2")) {
    return AHIS_UNKNOWN;
}

static uint32_t __attribute__((used)) intAHIsub_SetSound(uint16_t channel asm("d0"), uint16_t sound asm("d1"), uint32_t offset asm("d2"), int32_t length asm("d3"), struct AHIAudioCtrlDrv *AudioCtrl asm("a2"), uint32_t flags asm("d4")) {
    return AHIS_UNKNOWN;
}

extern void __attribute__((used)) intAHIsub_Enable(struct AHIAudioCtrlDrv *AudioCtrl asm("a2"));
extern void __attribute__((used)) intAHIsub_Disable(struct AHIAudioCtrlDrv *AudioCtrl asm("a2"));

static uint32_t function_table[] = {
  (uint32_t)open,
  (uint32_t)close,
  (uint32_t)expunge,
  (uint32_t)null,
  (uint32_t)intAHIsub_AllocAudio,             // AllocAudio
  (uint32_t)intAHIsub_FreeAudio,              // FreeAudio
  (uint32_t)intAHIsub_Disable,                // Disable
  (uint32_t)intAHIsub_Enable,                 // Enable
  (uint32_t)intAHIsub_Start,                  // Start
  (uint32_t)intAHIsub_Update,                 // Update
  (uint32_t)intAHIsub_Stop,                   // Stop
  (uint32_t)intAHIsub_SetVol,                 // SetVol
  (uint32_t)intAHIsub_SetFreq,                // SetFreq
  (uint32_t)intAHIsub_SetSound,               // SetSound
  (uint32_t)intAHIsub_SetEffect,              // SetEffect
  (uint32_t)intAHIsub_LoadSound,              // LoadSound
  (uint32_t)intAHIsub_UnloadSound,            // UnloadSound
  (uint32_t)intAHIsub_GetAttr,                // GetAttr
  (uint32_t)intAHIsub_HardwareControl,        // HardwareControl
  (uint32_t)null,
  (uint32_t)null,
  (uint32_t)null,
  -1
};

const uint32_t auto_init_tables[4] = {
  sizeof(struct Library ), 
  (uint32_t)function_table,
  0,
  (uint32_t)init,
};
