The right way to use loadBankCustom

I want use my own file system to manage FMOD bank resources. My FileSystem can provide a stream for runtimanager.
‘loadBankCustom’ can load bank data with open, seek, read, close callbacks.But I did not really understand the correct method to use it.
1、What does BANK_INFO’s userdata refer to? Is a pointer to the bank resource data provided by my file system? In my case, should I convert my stream to IntPtr and assign it to UserData? When I did this, my program crashed.
2、My understanding of each callback is to open, seek, read, or close a stream or file in a callback. Is it correct?
3、What is the relationship between Handle and UserData in those callbacks? for example, in the FILE_OPEN_CALLBACK, (ref IntPtr handle, IntPtr userdata)

My example:

// get a stream from filesystem
Stream fmodStream = .........;

BANK_INFO info = new BANK_INFO();

info.opencallback = LoadedBank.OpenBankStreamCallback;
info.closecallback = LoadedBank.CloseBankStreamCallback;
info.seekcallback = LoadedBank.SeekBankStreamCallback;
info.readcallback = LoadedBank.ReadBankStreamCallback;

StreamToIntPtr(fmodStream, out info.userdata);
info.userdatalength = Marshal.SizeOf(info.userdata);

loadResult = StudioSystem.loadBankCustom(info, loadBankFlags, out bank);

#region

[MonoPInvokeCallback(typeof(FMOD.FILE_OPEN_CALLBACK))]
public static RESULT OpenBankStreamCallback(StringWrapper name, ref uint filesize, ref IntPtr handle, IntPtr userdata)
{
    handle = userdata;

    Stream stream = null;
    IntPtrToStream(handle, out stream);
    filesize = (uint)stream.Length;

    return RESULT.OK;
}

[MonoPInvokeCallback(typeof(FMOD.FILE_CLOSE_CALLBACK))]
public static RESULT CloseBankStreamCallback(IntPtr handle, IntPtr userdata)
{
    Stream stream = null;
     IntPtrToStream(handle, out stream);
     stream.Close();

     return RESULT.OK;
}

[MonoPInvokeCallback(typeof(FMOD.FILE_SEEK_CALLBACK))]
public static RESULT SeekBankStreamCallback(IntPtr handle, uint pos, IntPtr userdata)
{
    RESULT result = RESULT.ERR_FILE_NOTFOUND;
    Stream stream = null;
    IntPtrToStream(handle, out stream);
     var resPos = stream.Seek((long)pos, 0);
     if (resPos != -1)
         result = RESULT.OK;

     return result;
}

[MonoPInvokeCallback(typeof(FMOD.FILE_READ_CALLBACK))]
public static RESULT ReadBankStreamCallback(IntPtr handle, IntPtr buffer, uint sizebytes, ref uint bytesread, IntPtr userdata)
{
    RESULT result = RESULT.ERR_FILE_NOTFOUND;

     Stream stream = null;
     IntPtrToStream(handle, out stream);

      byte[] readBuffer = new byte[(int)sizebytes];
      uint bytesRead = (uint)stream.Read(readBuffer, 1, (int)sizebytes);
      buffer = ArrToPtr(readBuffer);

       bytesread = bytesRead;
           
       if (bytesRead != sizebytes)
       {
           result = RESULT.ERR_FILE_EOF;
       }
       else if (bytesRead > 0)
       {
           result = RESULT.OK;
        }

       return result;
}


#endregion

I have fixed the crash problem.
But The sizebytes in the FILE_READ_CALLBACK parameter is always 2048 bytes,which caused ERR_FORMAT exception. Shouldn’t this value be the length of the required bank file?
Anyone can help me? Thank you !
Here is the log

2048 is the default blockalign value, which is just the size of the buffer to write to, and not a complete block of memory for holding an entire bank. You could increase the blockalign size when calling System.setFileSystem but I don’t think that would fix the issue in this case.
Can you please share an updated code snippet so I can take a look at what might be going wrong?

Otherwise, I play sound with EventInstance.

Can you please share an updated code snippet to help me reproduce this issue?