protonscr

Analyze v17 DXVK state cache entry

dxvkclosed question
doitsujin/dxvk#3466 · opened 2023-05-31 by DatL4g · updated 2023-06-01 · 2 comments · github
DDatL4g 2023-05-31 github

I'm analyzing DXVK state cache files in Kotlin.

Basically I just need to know ByteArray size and shifting of the new header values.

struct DxvkStateCacheEntryHeader {
    uint32_t entryType : 1;		/* New */
    uint32_t stageMask : 5;		/* Changed */
    uint32_t entrySize : 26;	/* Changed */
};

My previous implementation (7 < Version < 17)

This information is probably not needed, however I'll just provide it here if so

DXVK state cache header

/* Reading state cache header, still working as nothing changed */

DxvkStateCacheHeader(
    val magic: String,
    val version: UInt,
    val entrySize: UInt
) {
    companion object {
        fun fromReader(reader: FileChannel): DxvkStateCacheHeader {
            val magic = ByteBuffer.allocate(4)
            reader.read(magic)

            return DxvkStateCacheHeader(
				magic = String(magic.array()),
				version = reader.readU32(ByteOrder.LITTLE_ENDIAN),
				entrySize = reader.readU32(ByteOrder.LITTLE_ENDIAN)
			)
        }
    }
}

DXVK entry header

/* Reading entry header, not working as it changed */

DxvkEntryHeader(
    val stageMask: UInt,
    val entrySize: UInt
) {
    companion object {
        fun fromReader(reader: FileChannel): DxvkEntryHeader {
            val magic = ByteBuffer.allocate(4)
            reader.read(magic)

            return DxvkEntryHeader(
				stageMask = reader.readU8(),
				entrySize = reader.readU24(ByteOrder.LITTLE_ENDIAN)
			)
        }
    }
}

Read(ing) methods

fun FileChannel.readU32(order: ByteOrder? = null): UInt {
    val bytes = ByteBuffer.allocate(4)
    order?.let { bytes.order(it) }
    val state = this.read(bytes)

    if (state == -1) {
        throw Exception("EOF while reading U32")
    }

    return bytes[0].unsignedInt(order) +
                bytes[1].unsignedShl(8, order) +
                bytes[2].unsignedShl(16, order) +
                bytes[3].unsignedShl(24, order)
}

fun FileChannel.readU24(order: ByteOrder? = null): UInt {
    val bytes = ByteBuffer.allocate(3)
    order?.let { bytes.order(it) }
    val state = this.read(bytes)

    if (state == -1) {
        throw Exception("EOF while reading U24")
    }

    return bytes[0].unsignedInt(order) +
                bytes[1].unsignedShl(8, order) +
                bytes[2].unsignedShl(16, order)
}

fun FileChannel.readU8(order: ByteOrder? = null): UInt {
    val bytes = ByteBuffer.allocate(1)
    order?.let { bytes.order(it) }
    val state = this.read(bytes)

    if (state == -1) {
        throw Exception("EOF while reading U8")
    }

    return bytes[0].unsignedInt(order)
}

Byte reading & shifting

fun Byte.unsignedInt(order: ByteOrder?): UInt {
    val byte = this.toUByte()
    return if (order == ByteOrder.LITTLE_ENDIAN) {
        byte.toUShort().toUInt()
    } else {
        byte.toUInt()
    }
}

fun Byte.unsignedShl(bitCount: Int, order: ByteOrder?): UInt {
    return unsignedInt(order).shl(bitCount)
}
Ddoitsujin maintainer 2023-05-31 github

DxvkStateCacheEntryHeader is a single 32-bit bitfield with entryType being stored in the least significant bit, then 5 bits for the stage mask (shifted by 1).

We should make this more explicit on our end actually since I don't think the exact layout is guaranteed per spec.

DDatL4g 2023-06-01 github

Aight got it, thanks.
Can be closed if you don't wanna keep it open for documentation purposes.

Nothing extracted yet.