HRESULT WaveBankReader::Impl::Open()

in Audio/WaveBankReader.cpp [505:914]


HRESULT WaveBankReader::Impl::Open(const wchar_t* szFileName) noexcept(false)
{
    Close();
    Clear();

    m_prepared = false;

    m_event.reset(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE));
    if (!m_event)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    CREATEFILE2_EXTENDED_PARAMETERS params = { sizeof(CREATEFILE2_EXTENDED_PARAMETERS), 0, 0, 0, {}, nullptr };
    params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
    params.dwFileFlags = FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN;
    ScopedHandle hFile(safe_handle(CreateFile2(szFileName,
                       GENERIC_READ,
                       FILE_SHARE_READ,
                       OPEN_EXISTING,
                       &params)));
#else
    ScopedHandle hFile(safe_handle(CreateFileW(szFileName,
                       GENERIC_READ,
                       FILE_SHARE_READ,
                       nullptr,
                       OPEN_EXISTING,
                       FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN,
                       nullptr)));
#endif

    if (!hFile)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // Read and verify header
    OVERLAPPED request = {};
    request.hEvent = m_event.get();

    bool wait = false;
    if (!ReadFile(hFile.get(), &m_header, sizeof(m_header), nullptr, &request))
    {
        DWORD error = GetLastError();
        if (error != ERROR_IO_PENDING)
            return HRESULT_FROM_WIN32(error);
        wait = true;
    }

    DWORD bytes;
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    BOOL result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
#else
    if (wait)
        std::ignore = WaitForSingleObject(m_event.get(), INFINITE);

    BOOL result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
#endif

    if (!result || (bytes != sizeof(m_header)))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    if (m_header.dwSignature != HEADER::SIGNATURE && m_header.dwSignature != HEADER::BE_SIGNATURE)
    {
        return E_FAIL;
    }

    bool be = (m_header.dwSignature == HEADER::BE_SIGNATURE);
    if (be)
    {
        DebugTrace("INFO: \"%ls\" is a big-endian (Xbox 360) wave bank\n", szFileName);
        m_header.BigEndian();
    }

    if (m_header.dwHeaderVersion != HEADER::VERSION)
    {
        return E_FAIL;
    }

    // Load bank data
    memset(&request, 0, sizeof(request));
    request.Offset = m_header.Segments[HEADER::SEGIDX_BANKDATA].dwOffset;
    request.hEvent = m_event.get();

    wait = false;
    if (!ReadFile(hFile.get(), &m_data, sizeof(m_data), nullptr, &request))
    {
        DWORD error = GetLastError();
        if (error != ERROR_IO_PENDING)
            return HRESULT_FROM_WIN32(error);
        wait = true;
    }

#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
#else
    if (wait)
        std::ignore = WaitForSingleObject(m_event.get(), INFINITE);

    result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
#endif

    if (!result || (bytes != sizeof(m_data)))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    if (be)
        m_data.BigEndian();

    if (!m_data.dwEntryCount)
    {
        return HRESULT_FROM_WIN32(ERROR_NO_DATA);
    }

    if (m_data.dwFlags & BANKDATA::TYPE_STREAMING)
    {
        if (m_data.dwAlignment < ALIGNMENT_DVD)
            return E_FAIL;
        if (m_data.dwAlignment % DVD_SECTOR_SIZE)
            return E_FAIL;
    }
    else if (m_data.dwAlignment < ALIGNMENT_MIN)
    {
        return E_FAIL;
    }

    if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
    {
        if (m_data.dwEntryMetaDataElementSize != sizeof(ENTRYCOMPACT))
        {
            return E_FAIL;
        }

        if (m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwLength > (MAX_COMPACT_DATA_SEGMENT_SIZE * m_data.dwAlignment))
        {
            // Data segment is too large to be valid compact wavebank
            return E_FAIL;
        }
    }
    else
    {
        if (m_data.dwEntryMetaDataElementSize != sizeof(ENTRY))
        {
            return E_FAIL;
        }
    }

    DWORD metadataBytes = m_header.Segments[HEADER::SEGIDX_ENTRYMETADATA].dwLength;
    if (metadataBytes != (m_data.dwEntryCount * m_data.dwEntryMetaDataElementSize))
    {
        return E_FAIL;
    }

    // Load names
    DWORD namesBytes = m_header.Segments[HEADER::SEGIDX_ENTRYNAMES].dwLength;
    if (namesBytes > 0)
    {
        if (namesBytes >= (m_data.dwEntryNameElementSize * m_data.dwEntryCount))
        {
            std::unique_ptr<char[]> temp(new (std::nothrow) char[namesBytes]);
            if (!temp)
                return E_OUTOFMEMORY;

            memset(&request, 0, sizeof(request));
            request.Offset = m_header.Segments[HEADER::SEGIDX_ENTRYNAMES].dwOffset;
            request.hEvent = m_event.get();

            wait = false;
            if (!ReadFile(hFile.get(), temp.get(), namesBytes, nullptr, &request))
            {
                DWORD error = GetLastError();
                if (error != ERROR_IO_PENDING)
                    return HRESULT_FROM_WIN32(error);
                wait = true;
            }

        #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
            result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
        #else
            if (wait)
                std::ignore = WaitForSingleObject(m_event.get(), INFINITE);

            result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
        #endif

            if (!result || (namesBytes != bytes))
            {
                return HRESULT_FROM_WIN32(GetLastError());
            }

            for (uint32_t j = 0; j < m_data.dwEntryCount; ++j)
            {
                DWORD n = m_data.dwEntryNameElementSize * j;

                char name[64] = {};
                strncpy_s(name, &temp[n], sizeof(name));

                m_names[name] = j;
            }
        }
    }

    // Load entries
    if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
    {
        m_entries.reset(reinterpret_cast<uint8_t*>(new (std::nothrow) ENTRYCOMPACT[m_data.dwEntryCount]));
    }
    else
    {
        m_entries.reset(reinterpret_cast<uint8_t*>(new (std::nothrow) ENTRY[m_data.dwEntryCount]));
    }
    if (!m_entries)
        return E_OUTOFMEMORY;

    memset(&request, 0, sizeof(request));
    request.Offset = m_header.Segments[HEADER::SEGIDX_ENTRYMETADATA].dwOffset;
    request.hEvent = m_event.get();

    wait = false;
    if (!ReadFile(hFile.get(), m_entries.get(), metadataBytes, nullptr, &request))
    {
        DWORD error = GetLastError();
        if (error != ERROR_IO_PENDING)
            return HRESULT_FROM_WIN32(error);
        wait = true;
    }

#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
#else
    if (wait)
        std::ignore = WaitForSingleObject(m_event.get(), INFINITE);

    result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
#endif

    if (!result || (metadataBytes != bytes))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    if (be)
    {
        if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
        {
            auto ptr = reinterpret_cast<ENTRYCOMPACT*>(m_entries.get());
            for (size_t j = 0; j < m_data.dwEntryCount; ++j, ++ptr)
                ptr->BigEndian();
        }
        else
        {
            auto ptr = reinterpret_cast<ENTRY*>(m_entries.get());
            for (size_t j = 0; j < m_data.dwEntryCount; ++j, ++ptr)
                ptr->BigEndian();
        }
    }

    // Load seek tables (XMA2 / xWMA)
    DWORD seekLen = m_header.Segments[HEADER::SEGIDX_SEEKTABLES].dwLength;
    if (seekLen > 0)
    {
        m_seekData.reset(new (std::nothrow) uint8_t[seekLen]);
        if (!m_seekData)
            return E_OUTOFMEMORY;

        memset(&request, 0, sizeof(OVERLAPPED));
        request.Offset = m_header.Segments[HEADER::SEGIDX_SEEKTABLES].dwOffset;
        request.hEvent = m_event.get();

        wait = false;
        if (!ReadFile(hFile.get(), m_seekData.get(), seekLen, nullptr, &request))
        {
            DWORD error = GetLastError();
            if (error != ERROR_IO_PENDING)
                return HRESULT_FROM_WIN32(error);
            wait = true;
        }

    #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
        result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
    #else
        if (wait)
            std::ignore = WaitForSingleObject(m_event.get(), INFINITE);

        result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
    #endif

        if (!result || (seekLen != bytes))
        {
            return HRESULT_FROM_WIN32(GetLastError());
        }

        if (be)
        {
            auto ptr = reinterpret_cast<uint32_t*>(m_seekData.get());
            for (size_t j = 0; j < seekLen; j += 4, ++ptr)
            {
                *ptr = _byteswap_ulong(*ptr);
            }
        }
    }

    DWORD waveLen = m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwLength;
    if (!waveLen)
    {
        return HRESULT_FROM_WIN32(ERROR_NO_DATA);
    }

    if (m_data.dwFlags & BANKDATA::TYPE_STREAMING)
    {
        // If streaming, reopen without buffering
        hFile.reset();

    #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
        CREATEFILE2_EXTENDED_PARAMETERS params2 = { sizeof(CREATEFILE2_EXTENDED_PARAMETERS), 0, 0, 0, {}, nullptr };
        params2.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
        params2.dwFileFlags = FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING;
        m_async = CreateFile2(szFileName,
                              GENERIC_READ,
                              FILE_SHARE_READ,
                              OPEN_EXISTING,
                              &params2);
    #else
        m_async = CreateFileW(szFileName,
                              GENERIC_READ,
                              FILE_SHARE_READ,
                              nullptr,
                              OPEN_EXISTING,
                              FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
                              nullptr);
    #endif

        if (m_async == INVALID_HANDLE_VALUE)
        {
            return HRESULT_FROM_WIN32(GetLastError());
        }

        m_prepared = true;
    }
    else
    {
        // If in-memory, kick off read of wave data
        void* dest = nullptr;

    #ifdef DIRECTX_ENABLE_XMA2
        bool xma = false;
        if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
        {
            if (m_data.CompactFormat.wFormatTag == MINIWAVEFORMAT::TAG_XMA)
                xma = true;
        }
        else
        {
            for (uint32_t j = 0; j < m_data.dwEntryCount; ++j)
            {
                auto& entry = reinterpret_cast<const ENTRY*>(m_entries.get())[j];
                if (entry.Format.wFormatTag == MINIWAVEFORMAT::TAG_XMA)
                {
                    xma = true;
                    break;
                }
            }
        }

        if (xma)
        {
            HRESULT hr = ApuAlloc(&m_xmaMemory, nullptr, waveLen, SHAPE_XMA_INPUT_BUFFER_ALIGNMENT);
            if (FAILED(hr))
            {
                DebugTrace("ERROR: ApuAlloc failed. Did you allocate a large enough heap with ApuCreateHeap for all your XMA wave data?\n");
                return hr;
            }

            dest = m_xmaMemory;
        }
        else
        #endif // XMA2
        {
            m_waveData.reset(new (std::nothrow) uint8_t[waveLen]);
            if (!m_waveData)
                return E_OUTOFMEMORY;

            dest = m_waveData.get();
        }

        memset(&m_request, 0, sizeof(OVERLAPPED));
        m_request.Offset = m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwOffset;
        m_request.hEvent = m_event.get();

        if (!ReadFile(hFile.get(), dest, waveLen, nullptr, &m_request))
        {
            DWORD error = GetLastError();
            if (error != ERROR_IO_PENDING)
                return HRESULT_FROM_WIN32(error);
        }
        else
        {
            m_prepared = true;
            memset(&m_request, 0, sizeof(OVERLAPPED));
        }

        m_async = hFile.release();
    }

    return S_OK;
}