int __cdecl wmain()

in XWBTool/xwbtool.cpp [1202:1926]


int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
{
    // Parameters and defaults
    wchar_t szOutputFile[MAX_PATH] = {};
    wchar_t szHeaderFile[MAX_PATH] = {};

    // Set locale for output since GetErrorDesc can get localized strings.
    std::locale::global(std::locale(""));

    // Process command line
    uint32_t dwOptions = 0;
    std::list<SConversion> conversion;

    for (int iArg = 1; iArg < argc; iArg++)
    {
        PWSTR pArg = argv[iArg];

        if (('-' == pArg[0]) || ('/' == pArg[0]))
        {
            pArg++;
            PWSTR pValue;

            for (pValue = pArg; *pValue && (':' != *pValue); pValue++);

            if (*pValue)
                *pValue++ = 0;

            uint32_t dwOption = LookupByName(pArg, g_pOptions);

            if (!dwOption || (dwOptions & (1 << dwOption)))
            {
                PrintUsage();
                return 1;
            }

            dwOptions |= 1 << dwOption;

            // Handle options with additional value parameter
            switch (dwOption)
            {
            case OPT_OUTPUTFILE:
            case OPT_OUTPUTHEADER:
            case OPT_FILELIST:
                if (!*pValue)
                {
                    if ((iArg + 1 >= argc))
                    {
                        PrintUsage();
                        return 1;
                    }

                    iArg++;
                    pValue = argv[iArg];
                }
                break;
            }

            switch (dwOption)
            {
            case OPT_OUTPUTFILE:
                wcscpy_s(szOutputFile, MAX_PATH, pValue);
                break;

            case OPT_OUTPUTHEADER:
                wcscpy_s(szHeaderFile, MAX_PATH, pValue);
                break;

            case OPT_ADVANCED_FORMAT:
                // Must disable compact version to support 4K
                if (dwOptions & (1 << OPT_COMPACT))
                {
                    wprintf(L"-c and -af are mutually exclusive options\n");
                    return 1;
                }
                dwOptions |= (1 << OPT_NOCOMPACT);
                break;

            case OPT_COMPACT:
                if (dwOptions & (1 << OPT_ADVANCED_FORMAT))
                {
                    wprintf(L"-c and -af are mutually exclusive options\n");
                    return 1;
                }
                if (dwOptions & (1 << OPT_NOCOMPACT))
                {
                    wprintf(L"-c and -nc are mutually exclusive options\n");
                    return 1;
                }
                break;

            case OPT_NOCOMPACT:
                if (dwOptions & (1 << OPT_COMPACT))
                {
                    wprintf(L"-c and -nc are mutually exclusive options\n");
                    return 1;
                }
                break;

            case OPT_FILELIST:
            {
                std::wifstream inFile(pValue);
                if (!inFile)
                {
                    wprintf(L"Error opening -flist file %ls\n", pValue);
                    return 1;
                }

                inFile.imbue(std::locale::classic());

                ProcessFileList(inFile, conversion);
            }
            break;
            }
        }
        else if (wcspbrk(pArg, L"?*") != nullptr)
        {
            size_t count = conversion.size();
            SearchForFiles(pArg, conversion, (dwOptions & (1 << OPT_RECURSIVE)) != 0);
            if (conversion.size() <= count)
            {
                wprintf(L"No matching files found for %ls\n", pArg);
                return 1;
            }
        }
        else
        {
            SConversion conv = {};
            wcscpy_s(conv.szSrc, MAX_PATH, pArg);

            conversion.push_back(conv);
        }
    }

    if (conversion.empty())
    {
        wprintf(L"ERROR: Need at least 1 wave file to build wave bank\n\n");
        PrintUsage();
        return 0;
    }

    if (~dwOptions & (1 << OPT_NOLOGO))
        PrintLogo();

    // Determine output file name
    if (!*szOutputFile)
    {
        auto pConv = conversion.begin();

        wchar_t ext[_MAX_EXT] = {};
        wchar_t fname[_MAX_FNAME] = {};
        _wsplitpath_s(pConv->szSrc, nullptr, 0, nullptr, 0, fname, _MAX_FNAME, ext, _MAX_EXT);

        if (_wcsicmp(ext, L".xwb") == 0)
        {
            wprintf(L"ERROR: Need to specify output file via -o\n");
            return 1;
        }

        _wmakepath_s(szOutputFile, nullptr, nullptr, fname, L".xwb");
    }

    if (dwOptions & (1 << OPT_TOLOWER))
    {
        std::ignore = _wcslwr_s(szOutputFile);

        if (*szHeaderFile)
        {
            std::ignore = _wcslwr_s(szHeaderFile);
        }
    }

    if (~dwOptions & (1 << OPT_OVERWRITE))
    {
        if (GetFileAttributesW(szOutputFile) != INVALID_FILE_ATTRIBUTES)
        {
            wprintf(L"ERROR: Output file %ls already exists, use -y to overwrite!\n", szOutputFile);
            return 1;
        }

        if (*szHeaderFile)
        {
            if (GetFileAttributesW(szHeaderFile) != INVALID_FILE_ATTRIBUTES)
            {
                wprintf(L"ERROR: Output header file %ls already exists!\n", szHeaderFile);
                return 1;
            }
        }
    }

    // Gather wave files
    std::unique_ptr<uint8_t[]> entries;
    std::unique_ptr<char[]> entryNames;
    std::vector<WaveFile> waves;
    MINIWAVEFORMAT compactFormat = {};

    bool xma = false;

    size_t index = 0;
    for (auto pConv = conversion.begin(); pConv != conversion.end(); ++pConv, ++index)
    {
        wchar_t ext[_MAX_EXT] = {};
        wchar_t fname[_MAX_FNAME] = {};
        _wsplitpath_s(pConv->szSrc, nullptr, 0, nullptr, 0, fname, _MAX_FNAME, ext, _MAX_EXT);

        // Load source image
        if (pConv != conversion.begin())
            wprintf(L"\n");

        wprintf(L"reading %ls", pConv->szSrc);
        fflush(stdout);

        WaveFile wave;
        wave.conv = index;
        std::unique_ptr<uint8_t[]> waveData;

        HRESULT hr = DirectX::LoadWAVAudioFromFileEx(pConv->szSrc, waveData, wave.data);
        if (FAILED(hr))
        {
            wprintf(L"\nERROR: Failed to load file (%08X%ls)\n", static_cast<unsigned int>(hr), GetErrorDesc(hr));
            return 1;
        }

        wave.waveData = std::move(waveData);

        PrintInfo(wave);

        if (wave.data.wfx->wFormatTag == WAVE_FORMAT_XMA2)
            xma = true;

        waves.emplace_back(std::move(wave));
    }

    wprintf(L"\n");

    DWORD dwAlignment = ALIGNMENT_MIN;
    if (dwOptions & (1 << OPT_STREAMING))
    {
        dwAlignment = (dwOptions & (1 << OPT_ADVANCED_FORMAT)) ? ALIGNMENT_ADVANCED_FORMAT : ALIGNMENT_DVD;
    }
    else if (xma)
    {
        // Xbox requires 2K alignment for XMA2
        dwAlignment = 2048 /* XMA_BYTES_PER_PACKET */;
    }

    // Convert wave format to miniformat, failing if any won't map
    // Check to see if we can use the compact wave bank format
    bool compact = (dwOptions & (1 << OPT_NOCOMPACT)) ? false : true;
    int reason = 0;
    uint64_t waveOffset = 0;

    for (auto it = waves.begin(); it != waves.end(); ++it)
    {
        if (!ConvertToMiniFormat(it->data.wfx, it->data.seek != nullptr, it->miniFmt))
        {
            auto cit = conversion.cbegin();
            advance(cit, it->conv);
            wprintf(L"ERROR: Failed encoding %ls\n", cit->szSrc);
            return 1;
        }

        if (it == waves.begin())
        {
            memcpy(&compactFormat, &it->miniFmt, sizeof(MINIWAVEFORMAT));
        }
        else if (memcmp(&compactFormat, &it->miniFmt, sizeof(MINIWAVEFORMAT)) != 0)
        {
            compact = false;
            reason |= 0x1;
        }

        if (it->data.loopLength > 0)
        {
            compact = false;
            reason |= 0x2;
        }

        DWORD alignedSize = BLOCKALIGNPAD(it->data.audioBytes, dwAlignment);
        waveOffset += alignedSize;
    }

    if (waveOffset > UINT32_MAX)
    {
        wprintf(L"ERROR: Audio wave data is too large to encode into wavebank (offset %llu)", waveOffset);
        return 1;
    }
    else if (waveOffset > (MAX_COMPACT_DATA_SEGMENT_SIZE * uint64_t(dwAlignment)))
    {
        compact = false;
        reason |= 0x4;
    }

    if ((dwOptions & (1 << OPT_COMPACT)) && !compact)
    {
        wprintf(L"ERROR: Cannot create compact wave bank:\n");
        if (reason & 0x1)
        {
            wprintf(L"- Mismatched formats. All formats must be identical for a compact wavebank.\n");
        }
        if (reason & 0x2)
        {
            wprintf(L"- Found loop points. Compact wavebanks do not support loop points.\n");
        }
        if (reason & 0x4)
        {
            wprintf(L"- Audio wave data is too large to encode in compact wavebank (%llu > %llu).\n", waveOffset, (uint64_t(MAX_COMPACT_DATA_SEGMENT_SIZE) * uint64_t(dwAlignment)));
        }
        return 1;
    }

    // Build entry metadata (and assign wave offset within data segment)
    // Build entry friendly names if requested
    entries.reset(new uint8_t[(compact ? sizeof(ENTRYCOMPACT) : sizeof(ENTRY)) * waves.size()]);

    if (dwOptions & (1 << OPT_FRIENDLY_NAMES))
    {
        entryNames.reset(new char[waves.size() * ENTRYNAME_LENGTH]);
        memset(entryNames.get(), 0, sizeof(char) * waves.size() * ENTRYNAME_LENGTH);
    }

    waveOffset = 0;
    size_t count = 0;
    size_t seekEntries = 0;
    for (auto it = waves.begin(); it != waves.end(); ++it, ++count)
    {
        DWORD alignedSize = BLOCKALIGNPAD(it->data.audioBytes, dwAlignment);

        auto wfx = it->data.wfx;

        uint64_t duration = 0;

        switch (it->miniFmt.wFormatTag)
        {
        case MINIWAVEFORMAT::TAG_XMA:
            if (it->data.seekCount > 0)
                seekEntries += size_t(it->data.seekCount) + 1u;

            duration = reinterpret_cast<const XMA2WAVEFORMATEX*>(wfx)->SamplesEncoded;
            break;

        case MINIWAVEFORMAT::TAG_ADPCM:
        {
            auto adpcmFmt = reinterpret_cast<const ADPCMEWAVEFORMAT*>(wfx);
            duration = (uint64_t(it->data.audioBytes) / uint64_t(wfx->nBlockAlign)) * uint64_t(adpcmFmt->wSamplesPerBlock);
            int partial = it->data.audioBytes % wfx->nBlockAlign;
            if (partial)
            {
                if (partial >= (7 * wfx->nChannels))
                    duration += (uint64_t(partial) * 2 / uint64_t(wfx->nChannels - 12));
            }
        }
        break;

        case MINIWAVEFORMAT::TAG_WMA:
            if (it->data.seekCount > 0)
            {
                seekEntries += size_t(it->data.seekCount) + 1u;
                duration = it->data.seek[it->data.seekCount - 1] / uint32_t(2 * wfx->nChannels);
            }
            break;

        default: // MINIWAVEFORMAT::TAG_PCM
            duration = (uint64_t(it->data.audioBytes) * 8) / (uint64_t(wfx->wBitsPerSample) * uint64_t(wfx->nChannels));
            break;
        }

        if (compact)
        {
            auto entry = reinterpret_cast<ENTRYCOMPACT*>(entries.get() + count * sizeof(ENTRYCOMPACT));
            memset(entry, 0, sizeof(ENTRYCOMPACT));

            assert(waveOffset <= (MAX_COMPACT_DATA_SEGMENT_SIZE * uint64_t(dwAlignment)));
            entry->dwOffset = uint32_t(waveOffset / dwAlignment);

            assert(dwAlignment <= 2048);
            entry->dwLengthDeviation = alignedSize - it->data.audioBytes;
        }
        else
        {
            auto entry = reinterpret_cast<ENTRY*>(entries.get() + count * sizeof(ENTRY));
            memset(entry, 0, sizeof(ENTRY));

            if (duration > 268435455)
            {
                wprintf(L"ERROR: Duration of audio too long to encode into wavebank (%llu > 2^28))\n", duration);
                return 1;
            }

            entry->Duration = uint32_t(duration);
            memcpy(&entry->Format, &it->miniFmt, sizeof(MINIWAVEFORMAT));
            entry->PlayRegion.dwOffset = uint32_t(waveOffset);
            entry->PlayRegion.dwLength = it->data.audioBytes;

            if (it->data.loopLength > 0)
            {
                entry->LoopRegion.dwStartSample = it->data.loopStart;
                entry->LoopRegion.dwTotalSamples = it->data.loopLength;
            }
        }

        if (dwOptions & (1 << OPT_FRIENDLY_NAMES))
        {
            auto cit = conversion.cbegin();
            advance(cit, it->conv);

            wchar_t wEntryName[_MAX_FNAME] = {};
            _wsplitpath_s(cit->szSrc, nullptr, 0, nullptr, 0, wEntryName, _MAX_FNAME, nullptr, 0);

            int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, wEntryName, -1, &entryNames[count * ENTRYNAME_LENGTH], ENTRYNAME_LENGTH, nullptr, nullptr);
            if (result <= 0)
            {
                memset(&entryNames[count * ENTRYNAME_LENGTH], 0, ENTRYNAME_LENGTH);
            }
        }

        waveOffset += alignedSize;
    }

    assert(count > 0 && count == waves.size());

    // Create wave bank
    assert(*szOutputFile != 0);

    wprintf(L"writing %ls%ls wavebank %ls w/ %zu entries\n", (compact) ? L"compact " : L"", (dwOptions & (1 << OPT_STREAMING)) ? L"streaming" : L"in-memory", szOutputFile, waves.size());
    fflush(stdout);

    ScopedHandle hFile(safe_handle(CreateFileW(szOutputFile, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)));
    if (!hFile)
    {
        wprintf(L"ERROR: Failed opening output file %ls, %lu\n", szOutputFile, GetLastError());
        return 1;
    }

    // Setup wave bank header
    HEADER header = {};
    header.dwSignature = HEADER::SIGNATURE;
    header.dwHeaderVersion = HEADER::VERSION;
    header.dwVersion = XACT_CONTENT_VERSION;

    DWORD segmentOffset = sizeof(HEADER);

    // Write bank metadata
    assert((segmentOffset % 4) == 0);

    BANKDATA data = {};

    data.dwEntryCount = uint32_t(waves.size());
    data.dwAlignment = dwAlignment;

    GetSystemTimeAsFileTime(&data.BuildTime);

    data.dwFlags = (dwOptions & (1 << OPT_STREAMING)) ? BANKDATA::TYPE_STREAMING : BANKDATA::TYPE_BUFFER;

    if (seekEntries > 0)
    {
        data.dwFlags |= BANKDATA::FLAGS_SEEKTABLES;
    }

    if (dwOptions & (1 << OPT_FRIENDLY_NAMES))
    {
        data.dwFlags |= BANKDATA::FLAGS_ENTRYNAMES;
        data.dwEntryNameElementSize = ENTRYNAME_LENGTH;
    }

    if (compact)
    {
        data.dwFlags |= BANKDATA::FLAGS_COMPACT;
        data.dwEntryMetaDataElementSize = sizeof(ENTRYCOMPACT);
        memcpy(&data.CompactFormat, &compactFormat, sizeof(MINIWAVEFORMAT));
    }
    else
    {
        data.dwEntryMetaDataElementSize = sizeof(ENTRY);
    }

    {
        wchar_t wBankName[_MAX_FNAME] = {};
        _wsplitpath_s(szOutputFile, nullptr, 0, nullptr, 0, wBankName, _MAX_FNAME, nullptr, 0);

        int result = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, wBankName, -1, data.szBankName, BANKDATA::BANKNAME_LENGTH, nullptr, nullptr);
        if (result <= 0)
        {
            memset(data.szBankName, 0, BANKDATA::BANKNAME_LENGTH);
        }
    }

    if (SetFilePointer(hFile.get(), LONG(segmentOffset), nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
    {
        wprintf(L"ERROR: Failed writing bank data to %ls, SFP %lu\n", szOutputFile, GetLastError());
        return 1;
    }

    DWORD bytesWritten;
    if (!WriteFile(hFile.get(), &data, sizeof(data), &bytesWritten, nullptr)
        || bytesWritten != sizeof(data))
    {
        wprintf(L"ERROR: Failed writing bank data to %ls, %lu\n", szOutputFile, GetLastError());
        return 1;
    }

    header.Segments[HEADER::SEGIDX_BANKDATA].dwOffset = segmentOffset;
    header.Segments[HEADER::SEGIDX_BANKDATA].dwLength = sizeof(BANKDATA);
    segmentOffset += sizeof(BANKDATA);

    // Write entry metadata
    assert((segmentOffset % 4) == 0);

    if (SetFilePointer(hFile.get(), LONG(segmentOffset), nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
    {
        wprintf(L"ERROR: Failed writing entry metadata to %ls, SFP %lu\n", szOutputFile, GetLastError());
        return 1;
    }

    uint32_t entryBytes = uint32_t(waves.size() * data.dwEntryMetaDataElementSize);
    if (!WriteFile(hFile.get(), entries.get(), entryBytes, &bytesWritten, nullptr)
        || bytesWritten != entryBytes)
    {
        wprintf(L"ERROR: Failed writing entry metadata to %ls, %lu\n", szOutputFile, GetLastError());
        return 1;
    }

    header.Segments[HEADER::SEGIDX_ENTRYMETADATA].dwOffset = segmentOffset;
    header.Segments[HEADER::SEGIDX_ENTRYMETADATA].dwLength = entryBytes;
    segmentOffset += entryBytes;

    // Write seek tables
    assert((segmentOffset % 4) == 0);

    header.Segments[HEADER::SEGIDX_SEEKTABLES].dwOffset = segmentOffset;

    if (seekEntries > 0)
    {
        seekEntries += waves.size(); // Room for an offset per entry

        auto seekTables = std::make_unique<uint32_t[]>(seekEntries);

        if (SetFilePointer(hFile.get(), LONG(segmentOffset), nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
        {
            wprintf(L"ERROR: Failed writing seek tables to %ls, SFP %lu\n", szOutputFile, GetLastError());
            return 1;
        }

        uint32_t seekoffset = 0;
        uint32_t windex = 0;
        for (auto it = waves.begin(); it != waves.end(); ++it, ++windex)
        {
            if (it->miniFmt.wFormatTag == MINIWAVEFORMAT::TAG_WMA)
            {
                seekTables[windex] = seekoffset * sizeof(uint32_t);

                uint32_t baseoffset = uint32_t(waves.size() + seekoffset);
                seekTables[baseoffset] = it->data.seekCount;

                for (uint32_t j = 0; j < it->data.seekCount; ++j)
                {
                    seekTables[size_t(baseoffset) + size_t(j) + 1u] = it->data.seek[j];
                }

                seekoffset += size_t(it->data.seekCount) + 1u;
            }
            else if (it->miniFmt.wFormatTag == MINIWAVEFORMAT::TAG_XMA)
            {
                seekTables[windex] = seekoffset * sizeof(uint32_t);

                uint32_t baseoffset = uint32_t(waves.size() + seekoffset);
                seekTables[baseoffset] = it->data.seekCount;

                for (uint32_t j = 0; j < it->data.seekCount; ++j)
                {
                    seekTables[size_t(baseoffset) + size_t(j) + 1u] = _byteswap_ulong(it->data.seek[j]);
                }

                seekoffset += it->data.seekCount + 1;
            }
            else
            {
                seekTables[windex] = uint32_t(-1);
            }
        }

        uint32_t seekLen = uint32_t(sizeof(uint32_t) * seekEntries);

        if (!WriteFile(hFile.get(), seekTables.get(), seekLen, &bytesWritten, nullptr)
            || bytesWritten != seekLen)
        {
            wprintf(L"ERROR: Failed writing seek tables to %ls, %lu\n", szOutputFile, GetLastError());
            return 1;
        }

        segmentOffset += seekLen;

        header.Segments[HEADER::SEGIDX_SEEKTABLES].dwLength = seekLen;
    }
    else
    {
        header.Segments[HEADER::SEGIDX_SEEKTABLES].dwLength = 0;
    }

    // Write entry names
    if (dwOptions & (1 << OPT_FRIENDLY_NAMES))
    {
        assert((segmentOffset % 4) == 0);

        if (SetFilePointer(hFile.get(), LONG(segmentOffset), nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
        {
            wprintf(L"ERROR: Failed writing friendly entry names to %ls, SFP %lu\n", szOutputFile, GetLastError());
            return 1;
        }

        uint32_t entryNamesBytes = uint32_t(count * data.dwEntryNameElementSize);
        if (!WriteFile(hFile.get(), entryNames.get(), entryNamesBytes, &bytesWritten, nullptr)
            || bytesWritten != entryNamesBytes)
        {
            wprintf(L"ERROR: Failed writing friendly entry names to %ls, %lu\n", szOutputFile, GetLastError());
            return 1;
        }

        header.Segments[HEADER::SEGIDX_ENTRYNAMES].dwOffset = segmentOffset;
        header.Segments[HEADER::SEGIDX_ENTRYNAMES].dwLength = entryNamesBytes;
        segmentOffset += entryNamesBytes;
    }

    // Write wave data
    segmentOffset = BLOCKALIGNPAD(segmentOffset, dwAlignment);

    header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwOffset = segmentOffset;
    header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwLength = uint32_t(waveOffset);

    for (auto& it : waves)
    {
        if (SetFilePointer(hFile.get(), LONG(segmentOffset), nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
        {
            wprintf(L"ERROR: Failed writing audio data to %ls, SFP %lu\n", szOutputFile, GetLastError());
            return 1;
        }

        if (!WriteFile(hFile.get(), it.data.startAudio, it.data.audioBytes, &bytesWritten, nullptr)
            || bytesWritten != it.data.audioBytes)
        {
            wprintf(L"ERROR: Failed writing audio data to %ls, %lu\n", szOutputFile, GetLastError());
            return 1;
        }

        DWORD alignedSize = BLOCKALIGNPAD(it.data.audioBytes, dwAlignment);

        if ((uint64_t(segmentOffset) + alignedSize) > UINT32_MAX)
        {
            wprintf(L"ERROR: Data exceeds maximum size for wavebank\n");
            return 1;
        }

        segmentOffset += alignedSize;
    }

    assert(segmentOffset == (header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwOffset + waveOffset));

    // Commit wave bank
    if (SetFilePointer(hFile.get(), LONG(segmentOffset), nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
    {
        wprintf(L"ERROR: Failed committing output file %ls, EOF %lu\n", szOutputFile, GetLastError());
        return 1;
    }

    if (!SetEndOfFile(hFile.get()))
    {
        wprintf(L"ERROR: Failed committing output file %ls, EOF %lu\n", szOutputFile, GetLastError());
        return 1;
    }

    if (SetFilePointer(hFile.get(), 0, nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
    {
        wprintf(L"ERROR: Failed committing output file %ls, HDR %lu\n", szOutputFile, GetLastError());
        return 1;
    }

    if (!WriteFile(hFile.get(), &header, sizeof(header), &bytesWritten, nullptr)
        || bytesWritten != sizeof(header))
    {
        wprintf(L"ERROR: Failed committing output file %ls, HDR %lu\n", szOutputFile, GetLastError());
        return 1;
    }

    // Write C header if requested
    if (*szHeaderFile)
    {
        wprintf(L"writing C header %ls\n", szHeaderFile);
        fflush(stdout);

        FILE* file = nullptr;
        if (!_wfopen_s(&file, szHeaderFile, L"wt"))
        {
            wchar_t wBankName[_MAX_FNAME] = {};
            _wsplitpath_s(szOutputFile, nullptr, 0, nullptr, 0, wBankName, _MAX_FNAME, nullptr, 0);

            FileNameToIdentifier(wBankName, _MAX_FNAME);

            fprintf_s(file, "#pragma once\n\nenum XACT_WAVEBANK_%ls : unsigned int\n{\n", wBankName);

            size_t windex = 0;
            for (auto it = waves.begin(); it != waves.end(); ++it, ++windex)
            {
                auto cit = conversion.cbegin();
                advance(cit, it->conv);

                wchar_t wEntryName[_MAX_FNAME] = {};
                _wsplitpath_s(cit->szSrc, nullptr, 0, nullptr, 0, wEntryName, _MAX_FNAME, nullptr, 0);

                FileNameToIdentifier(wEntryName, _MAX_FNAME);

                fprintf_s(file, "    XACT_WAVEBANK_%ls_%ls = %zu,\n", wBankName, wEntryName, windex);
            }

            fprintf_s(file, "};\n\n#define XACT_WAVEBANK_%ls_ENTRY_COUNT %zu\n", wBankName, count);

            fclose(file);
        }
        else
        {
            wprintf(L"ERROR: Failed writing wave bank C header %ls\n", szHeaderFile);
            return 1;
        }
    }

    return 0;
}