port this code to C using SDL3
main.py:
from screen import SSLib
from cpu import Chip8
import time

class Emulator:
    def __init__(self):
        self.cpu = Chip8()
        self.sslib = SSLib((64, 32), 29)
        self.font = [
            0xF0, 0x90, 0x90, 0x90, 0xF0, # 0
            0x20, 0x60, 0x20, 0x20, 0x70, # 1
            0xF0, 0x10, 0xF0, 0x80, 0xF0, # 2
            0xF0, 0x10, 0xF0, 0x10, 0xF0, # 3
            0x90, 0x90, 0xF0, 0x10, 0x10, # 4
            0xF0, 0x80, 0xF0, 0x10, 0xF0, # 5
            0xF0, 0x80, 0xF0, 0x90, 0xF0, # 6
            0xF0, 0x10, 0x20, 0x40, 0x40, # 7
            0xF0, 0x90, 0xF0, 0x90, 0xF0, # 8
            0xF0, 0x90, 0xF0, 0x10, 0xF0, # 9
            0xF0, 0x90, 0xF0, 0x90, 0x90, # A
            0xE0, 0x90, 0xE0, 0x90, 0xE0, # B
            0xF0, 0x80, 0x80, 0x80, 0xF0, # C
            0xE0, 0x90, 0x90, 0x90, 0xE0, # D
            0xF0, 0x80, 0xF0, 0x80, 0xF0, # E
            0xF0, 0x80, 0xF0, 0x80, 0x80  # F
        ]

        self.key_dict = {
            '':         0xFF,
            '[1]':      1,
            '[2]':      2,
            '[3]':      3,
            '[4]':      4,
            '[5]':      5,
            '[6]':      6,
            '[7]':      7,
            '[8]':      8,
            '[9]':      9,
            '[/]':      0xA,
            '[*]':      0xB,
            '[-]':      0xC,
            '[+]':      0xD,
            'enter':    0xE,
            '[.]':      0xF,
        }

    def run(self, program):
        # load font
        for i, v in enumerate(self.font):
            self.cpu.memory[0x50 + i] = v

        # load program
        for i, v in enumerate(program):
            self.cpu.memory[0x200 + i] = v

        # run
        while True:
            data = self.sslib.update()
            
            if data[0]: self.sslib.terminate(); break
            self.cpu.key_down   = data[1]

            try:
                self.cpu.key = self.key_dict[data[2]]
            except KeyError:
                pass

            if data[2] == 'd': # dump cpu state
                print('CPU STATE')
                for i, v, in enumerate(self.cpu.V):
                    print('  - V{:01X} = 0x{:04x}'.format(i, v))
                print('  - PC = 0x{:04x}'.format(self.cpu.pc))
                print('  - I  = 0x{:04x}'.format(self.cpu.I))
                print('  - DT = 0x{:04x}'.format(self.cpu.delay_timer))
                print('  - ST = 0x{:04x}'.format(self.cpu.sound_timer))
            
            self.cpu.execute(self.cpu.decode(self.cpu.fetch()))
            
            clock = time.perf_counter() * 700
            sleep = int(clock) + 1 - clock
            time.sleep(sleep/700)

            self.sslib.draw(self.cpu.display)

if __name__ == '__main__':
    with open('3-corax+.ch8', 'rb') as f:
        program = f.read()

    e = Emulator()
    e.run(program)

cpu.py:
from dataclasses import dataclass, field
import random
import array

class OverflowArray(array.array):
    def __setitem__(self, index, value):
        max_value = (1 << (self.itemsize * 8)) - 1
        value = value & max_value
        super().__setitem__(index, value)

class Instruction:
    def __init__(self, value):
        self.value  = value
        self.opcode = (self.value & 0b1111000000000000) >> 12
        self.X      = (self.value & 0b0000111100000000) >> 8
        self.Y      = (self.value & 0b0000000011110000) >> 4
        self.N      = (self.value & 0b0000000000001111) >> 0
        self.NN     = (self.value & 0b0000000011111111) >> 0
        self.NNN    = (self.value & 0b0000111111111111) >> 0

class Chip8:
    def __init__(self):      
        self.memory         = OverflowArray('B', [0] * 4096)
        self.display        = OverflowArray('B', [0] * (64 * 32))
        self.stack          = OverflowArray('H', [0] * 32) # 32 stack entries
        self.V              = OverflowArray('B', [0] * 16)
        self.I              = 0
        self.pc             = 0x200
        self.delay_timer    = 0
        self.sound_timer    = 0
        self.key            = 0
        self.key_down       = False
        self.quirks         = {
            'COSMAC_SHIFT':         True,
            'COSMAC_JUMP_TABLE':    True,
            'AMIGA_FX1E_VF':        True,
            'STORE_TEMP_VAR':       True,
        }

    def fetch(self):
        instruction = (self.memory[self.pc] << 8) | self.memory[self.pc + 1]
        self.pc += 2
        return instruction

    def decode(self, value):
        x = Instruction(value)
        return x

    def execute(self, instruction):
        if self.delay_timer > 0: self.delay_timer -= 1
        if self.sound_timer > 0:
            print('beeeep')
            self.sound_timer -= 1
        
        u16mask = (1 << 16) - 1
        
        match instruction.opcode:
            case 0x0: # 0 - misc operations
                match instruction.NNN:
                    case 0x0E0: # 00E0 - clear screen
                        self.display = OverflowArray('B', [0] * (64 * 32))
                    case 0x0EE: # 00EE - return from subroutine
                        self.pc = self.stack.pop()
                    case _:
                        pass

            case 0x1: # 1NNN - jump
                self.pc = instruction.NNN
                
            case 0x2: # 2NNN - call subroutine
                self.stack.append(self.pc)
                self.pc = instruction.NNN
                
            case 0x3: # 3XNN - skip if vx == nn
                if self.V[instruction.X] == instruction.NN:
                    self.pc += 2
                
            case 0x4: # 4XNN - skip if vx != nn
                if self.V[instruction.X] != instruction.NN:
                    self.pc += 2
                
            case 0x5: # 5XY0 - skip if vx == vy
                if self.V[instruction.X] == self.V[instruction.Y]:
                    self.pc += 2
                
            case 0x6: # 6XNN - set VX
                self.V[instruction.X] = instruction.NN
                
            case 0x7: # 7XNN - add to VX
                self.V[instruction.X] += instruction.NN

            case 0x8: # 8XYN - logic and arithmetic group
                match instruction.N:
                    case 0x0: # 8XY0 - set vx = vy
                        self.V[instruction.X] = self.V[instruction.Y]

                    case 0x1: # 8XY1 - vx = vx | vy
                        self.V[instruction.X] = self.V[instruction.X] | self.V[instruction.Y]

                    case 0x2: # 8XY2 - vx = vx & vy
                        self.V[instruction.X] = self.V[instruction.X] & self.V[instruction.Y]

                    case 0x3: # 8XY3 - vx = vx ^ vy
                        self.V[instruction.X] = self.V[instruction.X] ^ self.V[instruction.Y]

                    case 0x4: # 8XY4 - vx = vx + vy, vf = carry
                        op = self.V[instruction.X] + self.V[instruction.Y]
                        self.V[0xF] = 1 if op > 255 else 0
                        self.V[instruction.X] = op

                    case 0x5: # 8XY5 - vx = vx - vy
                        op = self.V[instruction.X] - self.V[instruction.Y]
                        self.V[0xF] = 0 if op > 255 else 1
                        self.V[instruction.X] = op

                    case 0x6: # 8XY6 - vx = vx >> 1
                        if self.quirks['COSMAC_SHIFT']: self.V[instruction.X] = self.V[instruction.Y]
                        self.V[instruction.X] = (self.V[instruction.X] >> 1) & u16mask
                        self.V[0xF] = 1 if self.V[instruction.X] & 1 == 1 else 0

                    case 0x7: # 8XY7 -  vx = vy - vx
                        op = self.V[instruction.Y] - self.V[instruction.X]
                        self.V[0xF] = 0 if op > 255 else 1
                        self.V[instruction.X] = op

                    case 0xE: # 8XYE - vx = vx << 1
                        if self.quirks['COSMAC_SHIFT']: self.V[instruction.X] = self.V[instruction.Y]
                        self.V[instruction.X] = (self.V[instruction.X] << 1) & u16mask
                        self.V[0xF] = 1 if (self.V[instruction.X] & 0b10) >> 1 == 1 else 0
                
            case 0x9: # 9XY0 - skip if vx != vy
                if self.V[instruction.X] != self.V[instruction.Y]:
                    self.pc += 2
                
            case 0xA: # ANNN - set I
                self.I = instruction.NNN

            case 0xB: # BNNN/BXNN - pc = nnn + v0 / pc = nnn + vx
                if self.quirks['COSMAC_JUMP_TABLE']: self.pc = instruction.NNN + self.V[0]
                else: self.pc = instruction.NNN + self.V[instruction.X]

            case 0xC: # CXNN - vx = random & nn
                self.V[instruction.X] = random.randint(0x00, 0xFF) & instruction.NN
                
            case 0xD: # DXYN - display
                self.draw(instruction.X, instruction.Y, instruction.N)

            case 0xE: # EXNN - key group
                match instruction.NN:
                    case 0x9E: # EX9E - skip if key in vx is pressed
                        if self.key_down and self.key == self.V[instruction.X]: self.pc += 2

                    case 0xA1: # EX9E - skip if key in vx is not pressed
                        if self.key != self.V[instruction.X]: self.pc += 2

            case 0xF: # FXNN - misc instructions
                match instruction.NN:
                    case 0x07: # FX07 - vx = delay timer
                        self.V[instruction.X] = self.delay_timer

                    case 0x15: # FX15 - delay timer = vx
                        self.delay_timer = self.V[instruction.X]

                    case 0x18: # FX18 - sound timer = vx
                        self.sound_timer = self.V[instruction.X]

                    case 0x1E: # FX1E - i = i + vx
                        self.I += self.V[instruction.X]
                        if self.quirks['AMIGA_FX1E_VF']: self.V[0xF] = self.I > 0x0FFF

                    case 0x0A: # FX0A - wait for key
                        if not self.key_down:
                            self.pc -= 2
                        else:
                            self.V[instruction.X] = self.key

                    case 0x29: # FX29 - i = font character
                        self.I = 0x50 + (self.V[instruction.X] * 5)

                    case 0x33: # FX33 - BCD
                        for i, c in enumerate(str(self.V[instruction.X])):
                            self.memory[self.I + i] = int(c)

                    case 0x55: # FX55 - store v0 - vx into memory[i]
                        for i in range(instruction.X):
                            self.memory[self.I + i] = self.V[i]
                        
                        if not self.quirks['STORE_TEMP_VAR']: self.I = self.I + instruction.X + 1

                    case 0x65: # FX65 - load v0 - vx from memory[i]
                        for i in range(instruction.X):
                            self.V[i] = self.memory[self.I + i] 
                        
                        if not self.quirks['STORE_TEMP_VAR']: self.I = self.I + instruction.X + 1
            
            case _:
                print(f'UNRECOGNIZED INSTRUCTION\n  - V = {hex(instruction.value)}\n  - O = {hex(instruction.opcode)}\n  - X = {hex(instruction.X)}\n  - Y = {hex(instruction.Y)}\n  - N = {hex(instruction.N)}\n  - NN = {hex(instruction.NN)}\n  - NNN = {hex(instruction.NNN)}\n')

    def draw(self, X, Y, N):
        self.V[0xF] = 0
        y_pos = self.V[Y] % 32

        for row in range(N):
            x_pos = self.V[X] % 64

            sprite_byte = self.memory[self.I + row]
            bits = bin(sprite_byte)[2:].zfill(8)

            for bit in bits:
                if bit == '1':
                    index = x_pos + 64 * y_pos
                    
                    self.display[index] ^= 1
                    self.V[0xF] = self.display[index]

                x_pos += 1
                if x_pos > 63:
                    break

            y_pos += 1
            if y_pos > 31:
                break

screen.py:
import pygame

class SSLib:
    def __init__(self, reso, scalar):
        self.reso = reso
        self.scalar = scalar
        self.scaled = (reso[0] * scalar, reso[1] * scalar)
        self.screen = pygame.display.set_mode(self.scaled)
        self.pixel = pygame.Surface(reso)
        self.key_down = False

    def update(self):
        key = ''
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return (True, False, '')

            if event.type == pygame.KEYDOWN:
                self.key_down = True
                key = pygame.key.name(event.key)

            if event.type == pygame.KEYUP:
                self.key_down = False
                
        return (False, self.key_down, key)

    def draw(self, arr):
        self.screen.fill((255, 255, 255))
        
        for i, v in enumerate(arr):
            x = i % self.reso[0]
            y = i // self.reso[0]
            self.pixel.set_at((x, y), (255, 204, 1) if v else (153, 102, 1))

        pygame.transform.scale(self.pixel, self.scaled, self.screen)
        pygame.display.flip()

    def terminate(self):
        pygame.quit()