Tormented Souls 2

The same D3D12 guards on the colour-management library the game imports.

GPL-3.0-or-laterThis fix in MacGameFix

What you see

Fatal error! Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0xfffffffffffffff8 before the first frame

Why it happens

The game keeps only 16:9 resolutions and has no fallback when none exist

What the fix changes

Put 16:9 modes in the list the game reads

The code

Puts 16:9 modes into the display mode list the game reads, since it keeps only 16:9 ones and has no fallback when there are none.

fixes/d3d12-guards.c · my_modelist, lines 422–507
c
static HRESULT WINAPI my_modelist(void *self, UINT fmt, UINT flags, UINT *count, void *modes)
{
    HRESULT hr;
    UINT w = 0, h = 0, hz = 0, i, have, room, added = 0, big_w = 0, big_h = 0;

    if (!desktop_size(&w, &h, &hz)) return real_modelist(self, fmt, flags, count, modes);

    if (!modes)                                   /* the counting call */
    {
        hr = real_modelist(self, fmt, flags, count, NULL);
        if (SUCCEEDED(hr) && count) *count += ladder_fits(w, h);  /* room for ours */
        return hr;
    }

    /* The filling call. Hold back the slots we may need, fill the rest normally. */
    room = ladder_fits(w, h);
    have = *count;
    /* Never write past what the caller allocated. It asked for a count first
     * and this said "that many plus room", but a caller is free to hand back a
     * smaller buffer than it was told about, and appending into one would be a
     * far worse bug than the crash being repaired. */
    if (room > have) room = have;
    *count = have - room;
    hr = real_modelist(self, fmt, flags, count, modes);
    if (FAILED(hr)) { *count = have; return hr; }

    {
        /* Say what is actually on offer, with the right stride. A mode list
         * read at the wrong stride reports resolutions nobody offered, and a
         * conclusion drawn from it is worse than no conclusion. */
        static LONG told;
        UINT tallest = 0, widest = 0;
        for (i = 0; i < *count; ++i)
        {
            const unsigned char *m = (const unsigned char *)modes + (size_t)i * MODE_DESC_SIZE;
            UINT mw = *(const UINT *)(m + 0), mh = *(const UINT *)(m + 4);
            if (mh > tallest) tallest = mh;
            if (mw > widest) widest = mw;
        }
        if (InterlockedIncrement(&told) <= 4)
            logf_("mode list for format %u: %u modes, largest %ux%u; the screen is %ux%u",
                  fmt, *count, widest, tallest, w, h);
    }
    for (i = 0; i < *count; ++i)
    {
        const unsigned char *m = (const unsigned char *)modes + (size_t)i * MODE_DESC_SIZE;
        UINT mw = *(const UINT *)(m + 0), mh = *(const UINT *)(m + 4);
        double aspect;
        if (!mh) continue;
        aspect = (double)mw / (double)mh;
        if (aspect > ASPECT_LOW && aspect < ASPECT_HIGH)
        {
            static LONG said;
            if (InterlockedIncrement(&said) <= 4)
                logf_("        a 16:9 mode is already on offer (%ux%u), adding nothing",
                      mw, mh);
            return hr;
        }
    }
    /* Nothing on offer is 16:9 and this game keeps nothing else, so the array
     * it is about to build would come out empty. Give it the whole ladder that
     * fits, not just enough to survive: the player gets a resolution menu, not
     * a single forced entry. */
    for (i = 0; i < LADDER_N && added < room; ++i)
    {
        unsigned char *m;
        if ((ladder[i].w > w || ladder[i].h > h) && !(added == 0 && i + 1 == LADDER_N))
            continue;
        m = (unsigned char *)modes + (size_t)(*count) * MODE_DESC_SIZE;
        if (*count) memcpy(m, modes, MODE_DESC_SIZE);  /* inherit format and scaling */
        else memset(m, 0, MODE_DESC_SIZE);
        *(UINT *)(m + 0)  = ladder[i].w;
        *(UINT *)(m + 4)  = ladder[i].h;
        *(UINT *)(m + 8)  = hz;
        *(UINT *)(m + 12) = 1;
        *(UINT *)(m + 16) = fmt;
        *count += 1;
        if (!added) { big_w = ladder[i].w; big_h = ladder[i].h; }  /* largest first */
        ++added;
    }
    if (InterlockedIncrement(&modes_added) <= 8)
        logf_("not one of the %u modes on offer is 16:9, and this game keeps "
              "nothing else -- added %u of them, largest %ux%u @ %u Hz",
              *count - added, added, big_w, big_h, hz);
    return hr;
}

Details

Needs
D3DMetal, D3D12 (and D3D11 — it fails identically either way)
Installer
install-tormented-fix.sh
Executable
TormentedSouls2.exe
Built from
d3d12-guards.c
Verified on
Stable 26.3 with Game Porting Toolkit 4.0b2 (2026-08-31). The measurements below were made on crossover-preview-arm64-20260821, which is no longer a supported engine

The full story, with the measurements and the wrong turns