Advertisement
nonogamer9

OC-8 : A Chip-8 Emulator For OpenComputers (version 1.1)

Jul 21st, 2024 (edited)
300
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 10.60 KB | Gaming | 1 0
  1. local component = require("component")
  2. local computer = require("computer")
  3. local event = require("event")
  4. local gpu = component.gpu
  5. local keyboard = require("keyboard")
  6. local bit32 = require("bit32")
  7. local filesystem = require("filesystem")
  8.  
  9. gpu.setResolution(160, 50)
  10.  
  11. local chip8 = {
  12.   memory = {},
  13.   V = {},
  14.   I = 0,
  15.   PC = 0x200,
  16.   stack = {},
  17.   SP = 0,
  18.   delay_timer = 0,
  19.   sound_timer = 0,
  20.   keys = {},
  21.   draw_flag = false,
  22.   opcode = 0,
  23.   clock_speed = 700,
  24.   waiting_for_key = false,
  25.   key_register = 0,
  26.   shift_quirk = true,
  27.   jump_quirk = false,
  28.   load_store_quirk = true,
  29.   rom_end = 0,
  30. }
  31.  
  32. local width, height = 64, 32
  33. local scale_x, scale_y = 2, 1
  34. local buffer = {}
  35. local frame_buffer = {}
  36.  
  37. local font = {
  38.   0xF0, 0x90, 0x90, 0x90, 0xF0,
  39.   0x20, 0x60, 0x20, 0x20, 0x70,
  40.   0xF0, 0x10, 0xF0, 0x80, 0xF0,
  41.   0xF0, 0x10, 0xF0, 0x10, 0xF0,
  42.   0x90, 0x90, 0xF0, 0x10, 0x10,
  43.   0xF0, 0x80, 0xF0, 0x10, 0xF0,
  44.   0xF0, 0x80, 0xF0, 0x90, 0xF0,
  45.   0xF0, 0x10, 0x20, 0x40, 0x40,
  46.   0xF0, 0x90, 0xF0, 0x90, 0xF0,
  47.   0xF0, 0x90, 0xF0, 0x10, 0xF0,
  48.   0xF0, 0x90, 0xF0, 0x90, 0x90,
  49.   0xE0, 0x90, 0xE0, 0x90, 0xE0,
  50.   0xF0, 0x80, 0x80, 0x80, 0xF0,
  51.   0xE0, 0x90, 0x90, 0x90, 0xE0,
  52.   0xF0, 0x80, 0xF0, 0x80, 0xF0,
  53.   0xF0, 0x80, 0xF0, 0x80, 0x80
  54. }
  55.  
  56. function listCH8Files()
  57.   local files = {}
  58.   for file in filesystem.list("/home/") do
  59.     if file:match("%.ch8$") then
  60.       table.insert(files, file)
  61.     end
  62.   end
  63.   return files
  64. end
  65.  
  66. function chip8.init()
  67.   for i = 1, 4096 do chip8.memory[i] = 0 end
  68.   for i = 0, 15 do
  69.     chip8.V[i] = 0
  70.     chip8.keys[i] = false
  71.   end
  72.   for y = 1, height do
  73.     buffer[y] = {}
  74.     frame_buffer[y] = {}
  75.     for x = 1, width do
  76.       buffer[y][x] = 0
  77.       frame_buffer[y][x] = 0
  78.     end
  79.   end
  80.   for i = 1, #font do
  81.     chip8.memory[i] = font[i]
  82.   end
  83.   chip8.I = 0
  84.   chip8.PC = 0x200
  85.   chip8.SP = 0
  86.   chip8.delay_timer = 0
  87.   chip8.sound_timer = 0
  88.   chip8.draw_flag = true
  89.   chip8.opcode = 0
  90.   chip8.waiting_for_key = false
  91.   chip8.key_register = 0
  92.   chip8.rom_end = 0
  93. end
  94.  
  95. function chip8.loadROM(filename)
  96.   local file = io.open(filename, "rb")
  97.   if not file then error("Could not open ROM file: " .. filename) end
  98.   local address = 0x200
  99.   while true do
  100.     local byte = file:read(1)
  101.     if not byte then break end
  102.     chip8.memory[address] = string.byte(byte)
  103.     address = address + 1
  104.   end
  105.   file:close()
  106.   chip8.rom_end = address - 1
  107. end
  108.  
  109. function chip8.emulateCycle()
  110.   -- Prevent out-of-bounds or nil fetch
  111.   if chip8.PC < 1 or chip8.PC + 1 > 4096 or chip8.PC + 1 > chip8.rom_end then
  112.     error("Program counter out of bounds or end of ROM reached. Halting execution.")
  113.   end
  114.  
  115.   local op1 = chip8.memory[chip8.PC] or 0
  116.   local op2 = chip8.memory[chip8.PC + 1] or 0
  117.   chip8.opcode = bit32.lshift(op1, 8) + op2
  118.   chip8.PC = chip8.PC + 2
  119.  
  120.   local x = bit32.rshift(bit32.band(chip8.opcode, 0x0F00), 8)
  121.   local y = bit32.rshift(bit32.band(chip8.opcode, 0x00F0), 4)
  122.   local nnn = bit32.band(chip8.opcode, 0x0FFF)
  123.   local nn = bit32.band(chip8.opcode, 0x00FF)
  124.   local n = bit32.band(chip8.opcode, 0x000F)
  125.   local first_nibble = bit32.rshift(chip8.opcode, 12)
  126.  
  127.   if first_nibble == 0x0 then
  128.     if nn == 0xE0 then
  129.       for y = 1, height do for x = 1, width do buffer[y][x] = 0 end end
  130.       chip8.draw_flag = true
  131.     elseif nn == 0xEE then
  132.       chip8.SP = chip8.SP - 1
  133.       chip8.PC = chip8.stack[chip8.SP]
  134.     end
  135.   elseif first_nibble == 0x1 then
  136.     chip8.PC = nnn
  137.   elseif first_nibble == 0x2 then
  138.     chip8.stack[chip8.SP] = chip8.PC
  139.     chip8.SP = chip8.SP + 1
  140.     chip8.PC = nnn
  141.   elseif first_nibble == 0x3 then
  142.     if chip8.V[x] == nn then chip8.PC = chip8.PC + 2 end
  143.   elseif first_nibble == 0x4 then
  144.     if chip8.V[x] ~= nn then chip8.PC = chip8.PC + 2 end
  145.   elseif first_nibble == 0x5 then
  146.     if chip8.V[x] == chip8.V[y] then chip8.PC = chip8.PC + 2 end
  147.   elseif first_nibble == 0x6 then
  148.     chip8.V[x] = nn
  149.   elseif first_nibble == 0x7 then
  150.     chip8.V[x] = bit32.band(chip8.V[x] + nn, 0xFF)
  151.   elseif first_nibble == 0x8 then
  152.     if n == 0x0 then
  153.       chip8.V[x] = chip8.V[y]
  154.     elseif n == 0x1 then
  155.       chip8.V[x] = bit32.bor(chip8.V[x], chip8.V[y])
  156.     elseif n == 0x2 then
  157.       chip8.V[x] = bit32.band(chip8.V[x], chip8.V[y])
  158.     elseif n == 0x3 then
  159.       chip8.V[x] = bit32.bxor(chip8.V[x], chip8.V[y])
  160.     elseif n == 0x4 then
  161.       local sum = chip8.V[x] + chip8.V[y]
  162.       chip8.V[0xF] = sum > 0xFF and 1 or 0
  163.       chip8.V[x] = bit32.band(sum, 0xFF)
  164.     elseif n == 0x5 then
  165.       chip8.V[0xF] = chip8.V[x] > chip8.V[y] and 1 or 0
  166.       chip8.V[x] = bit32.band(chip8.V[x] - chip8.V[y], 0xFF)
  167.     elseif n == 0x6 then
  168.       if not chip8.shift_quirk then
  169.         chip8.V[x] = chip8.V[y]
  170.       end
  171.       chip8.V[0xF] = bit32.band(chip8.V[x], 0x1)
  172.       chip8.V[x] = bit32.rshift(chip8.V[x], 1)
  173.     elseif n == 0x7 then
  174.       chip8.V[0xF] = chip8.V[y] > chip8.V[x] and 1 or 0
  175.       chip8.V[x] = bit32.band(chip8.V[y] - chip8.V[x], 0xFF)
  176.     elseif n == 0xE then
  177.       if not chip8.shift_quirk then
  178.         chip8.V[x] = chip8.V[y]
  179.       end
  180.       chip8.V[0xF] = bit32.rshift(chip8.V[x], 7)
  181.       chip8.V[x] = bit32.band(bit32.lshift(chip8.V[x], 1), 0xFF)
  182.     end
  183.   elseif first_nibble == 0x9 then
  184.     if chip8.V[x] ~= chip8.V[y] then chip8.PC = chip8.PC + 2 end
  185.   elseif first_nibble == 0xA then
  186.     chip8.I = nnn
  187.   elseif first_nibble == 0xB then
  188.     if chip8.jump_quirk then
  189.       chip8.PC = nnn + chip8.V[x]
  190.     else
  191.       chip8.PC = nnn + chip8.V[0]
  192.     end
  193.   elseif first_nibble == 0xC then
  194.     chip8.V[x] = bit32.band(math.random(0, 255), nn)
  195.   elseif first_nibble == 0xD then
  196.     local x_coord = chip8.V[x] % width
  197.     local y_coord = chip8.V[y] % height
  198.     chip8.V[0xF] = 0
  199.     for row = 0, n-1 do
  200.       local sprite = chip8.memory[chip8.I + row] or 0
  201.       for col = 0, 7 do
  202.         if bit32.band(sprite, bit32.rshift(0x80, col)) ~= 0 then
  203.           local px, py = (x_coord + col) % width, (y_coord + row) % height
  204.           if buffer[py+1][px+1] == 1 then chip8.V[0xF] = 1 end
  205.           buffer[py+1][px+1] = bit32.bxor(buffer[py+1][px+1], 1)
  206.         end
  207.       end
  208.     end
  209.     chip8.draw_flag = true
  210.   elseif first_nibble == 0xE then
  211.     if nn == 0x9E then
  212.       if chip8.keys[chip8.V[x]] then chip8.PC = chip8.PC + 2 end
  213.     elseif nn == 0xA1 then
  214.       if not chip8.keys[chip8.V[x]] then chip8.PC = chip8.PC + 2 end
  215.     end
  216.   elseif first_nibble == 0xF then
  217.     if nn == 0x07 then
  218.       chip8.V[x] = chip8.delay_timer
  219.     elseif nn == 0x0A then
  220.       chip8.waiting_for_key = true
  221.       chip8.key_register = x
  222.     elseif nn == 0x15 then
  223.       chip8.delay_timer = chip8.V[x]
  224.     elseif nn == 0x18 then
  225.       chip8.sound_timer = chip8.V[x]
  226.     elseif nn == 0x1E then
  227.       chip8.I = chip8.I + chip8.V[x]
  228.     elseif nn == 0x29 then
  229.       chip8.I = chip8.V[x] * 5
  230.     elseif nn == 0x33 then
  231.       chip8.memory[chip8.I] = math.floor(chip8.V[x] / 100)
  232.       chip8.memory[chip8.I + 1] = math.floor((chip8.V[x] % 100) / 10)
  233.       chip8.memory[chip8.I + 2] = chip8.V[x] % 10
  234.     elseif nn == 0x55 then
  235.       for i = 0, x do
  236.         chip8.memory[chip8.I + i] = chip8.V[i]
  237.       end
  238.       if not chip8.load_store_quirk then
  239.         chip8.I = chip8.I + x + 1
  240.       end
  241.     elseif nn == 0x65 then
  242.       for i = 0, x do
  243.         chip8.V[i] = chip8.memory[chip8.I + i] or 0
  244.       end
  245.       if not chip8.load_store_quirk then
  246.         chip8.I = chip8.I + x + 1
  247.       end
  248.     end
  249.   else
  250.     error("Unknown opcode: " .. string.format("%04X", chip8.opcode))
  251.   end
  252. end
  253.  
  254. function chip8.updateTimers()
  255.   if chip8.delay_timer > 0 then chip8.delay_timer = chip8.delay_timer - 1 end
  256.   if chip8.sound_timer > 0 then
  257.     chip8.sound_timer = chip8.sound_timer - 1
  258.     if chip8.sound_timer == 0 then computer.beep(1000, 0.1) end
  259.   end
  260. end
  261.  
  262. function chip8.handleInput()
  263.   local keymap = {
  264.     ['1'] = 0x1, ['2'] = 0x2, ['3'] = 0x3, ['4'] = 0xC,
  265.     ['q'] = 0x4, ['w'] = 0x5, ['e'] = 0x6, ['r'] = 0xD,
  266.     ['a'] = 0x7, ['s'] = 0x8, ['d'] = 0x9, ['f'] = 0xE,
  267.     ['z'] = 0xA, ['x'] = 0x0, ['c'] = 0xB, ['v'] = 0xF
  268.   }
  269.  
  270.   for key, chip8_key in pairs(keymap) do
  271.     if keyboard.isKeyDown(keyboard.keys[key]) then
  272.       chip8.keys[chip8_key] = true
  273.     else
  274.       chip8.keys[chip8_key] = false
  275.     end
  276.   end
  277.  
  278.   if chip8.waiting_for_key then
  279.     for chip8_key, pressed in pairs(chip8.keys) do
  280.       if pressed then
  281.         chip8.V[chip8.key_register] = chip8_key
  282.         chip8.waiting_for_key = false
  283.         chip8.PC = chip8.PC + 2
  284.         break
  285.       end
  286.     end
  287.   end
  288. end
  289.  
  290. function chip8.updateScreen()
  291.   if chip8.draw_flag then
  292.     for y = 1, height do
  293.       for x = 1, width do
  294.         if frame_buffer[y][x] ~= buffer[y][x] then
  295.           local color = buffer[y][x] == 1 and 0xFFFFFF or 0x000000
  296.           gpu.setBackground(color)
  297.           gpu.fill((x-1)*scale_x+1, (y-1)*scale_y+1, scale_x, scale_y, " ")
  298.           frame_buffer[y][x] = buffer[y][x]
  299.         end
  300.       end
  301.     end
  302.     chip8.draw_flag = false
  303.   end
  304. end
  305.  
  306. function chip8.setQuirks(shift, jump, load_store)
  307.   chip8.shift_quirk = shift
  308.   chip8.jump_quirk = jump
  309.   chip8.load_store_quirk = load_store
  310. end
  311.  
  312. function chip8.run(rom_filename)
  313.   chip8.init()
  314.   chip8.loadROM(rom_filename)
  315.   chip8.setQuirks(true, false, true)
  316.  
  317.   gpu.setBackground(0x000000)
  318.   gpu.setForeground(0x000000)
  319.   gpu.fill(1, 1, 160, 50, " ")
  320.   gpu.setForeground(0xFFFFFF)
  321.  
  322.   chip8.updateScreen()
  323.  
  324.   local cycles_per_frame = 10
  325.   local frame_time = 1/60
  326.  
  327.   while true do
  328.     local start_time = computer.uptime()
  329.     for _ = 1, cycles_per_frame do
  330.       chip8.emulateCycle()
  331.       chip8.updateTimers()
  332.       chip8.handleInput()
  333.     end
  334.     chip8.updateScreen()
  335.     local elapsed = computer.uptime() - start_time
  336.     if elapsed < frame_time then
  337.       os.sleep(frame_time - elapsed)
  338.     end
  339.     if keyboard.isKeyDown(1) then break end
  340.   end
  341. end
  342.  
  343. print([[
  344.   @@@@@@   @@@@@@@           @@@@@@
  345.  @@!  @@@ !@@               @@!  @@@
  346.  @!@  !@! !@!      @!@!@!@!  !@!@!@
  347.  !!:  !!! :!!               !!:  !!!
  348.   : :. :   :: :: :           :.:: :
  349. ]])
  350. print("OC-8: CHIP-8 Emulator for OpenComputers coded by nonogamer9")
  351. print("Available ROMs in /home/:")
  352.  
  353. local roms = listCH8Files()
  354. for i, rom in ipairs(roms) do
  355.   print(i .. ". " .. rom)
  356. end
  357.  
  358. print("Enter the number of the ROM you want to run:")
  359. local choice = tonumber(io.read())
  360.  
  361. if choice and choice > 0 and choice <= #roms then
  362.   local rom_filename = "/home/" .. roms[choice]
  363.   chip8.run(rom_filename)
  364. else
  365.   print("Invalid selection. Emulator closed.")
  366. end
  367.  
  368. gpu.setBackground(0x000000)
  369. gpu.setForeground(0xFFFFFF)
  370. gpu.fill(1, 1, 160, 50, " ")
  371. print("OC-8 Emulator closed.")
  372.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement