Browse Source

first commit

tags/0.1.0
Adrian Siekierka 1 year ago
commit
abbf04fe98
17 changed files with 4388 additions and 0 deletions
  1. 53
    0
      README.md
  2. 2
    0
      autorun.lua
  3. 3
    0
      compile.sh
  4. 50
    0
      config_disks.lua
  5. 302
    0
      disks.lua
  6. 2032
    0
      emup.lua
  7. 41
    0
      kbdmaps.lua
  8. 160
    0
      keyboard.lua
  9. 62
    0
      pic.lua
  10. 123
    0
      pit.lua
  11. 113
    0
      platform.lua
  12. 493
    0
      platform_oc.lua
  13. 131
    0
      rotate.lua
  14. 1
    0
      startup.lua
  15. 258
    0
      table_cp437.lua
  16. 15
    0
      table_ocpalette.lua
  17. 549
    0
      video.lua

+ 53
- 0
README.md View File

@@ -0,0 +1,53 @@
# Lunatic86

Barely-IBM-compatible PC emulator written in Lua 5.3.

## Compilation Instructions

1. Run ./compile.sh on a (preferably?) Linux machine.
2. Edit disks.lua to point to your disk images.
3. Run platform_oc.lua in OpenComputers or Kallisti, or platform.lua with lcurses (text mode only).

## What's Emulated?

### Probably decently

* CPU: 8086-esque
* Graphics: CGA (640x200x1, 320x200x2, text modes), 320x200x8 (Mode 13h)
* With some partial I/O ports emulation!
* BIOS: High-level emulation
* Decent implementations of most expected interrupts

### Pretend

* Audio: PC Speaker (Computronics Beep Card)
* Keyboard: PC XT-compatible
* 8259 PIC
* 8253 PIT

### Not even tested

* Graphics: Tandy-compatible

### Not even implemented

* BIOS: Serial/Printer Port interrupts, INT 15h
* Pretty much anything not listed

## Notes

* platform.lua outputs debug text to stderr. Run with "2>/dev/null" to avoid.
* The performance on my desktop (i7-4790K) seems to be:
* about 3x PC XT in Lua 5.3,
* about 0.6x PC XT in latest stable OpenComputers,
* about 1x PC XT in latest stable OpenComputers + [fixed machine.lua](https://github.com/MightyPirates/OpenComputers/pull/2877) - you may override
it server-side,
* about 1.8x PC XT in latest stable OpenComputers + fixed machine.lua + natives recompiled to actually use -O2 and not debug mode,
* about 2.5x PC XT in Kallisti with the timeout-checking code disabled altogether, and about 1.8x PC XT otherwise.
* There's a lot of bugs and hacks.

## Known issues

Many. See the Wiki for the Compatibility List.

In particular, a "reduced memory usage" mode has to be introduced for OC users.

+ 2
- 0
autorun.lua View File

@@ -0,0 +1,2 @@

shell.execute("platform_oc.lua")

+ 3
- 0
compile.sh View File

@@ -0,0 +1,3 @@
#!/bin/sh
cpp emup.lua > emu.lua
sed -i -e "s/^#.*//g" emu.lua

+ 50
- 0
config_disks.lua View File

@@ -0,0 +1,50 @@
-- Uncomment this line if you're on OpenComputers/Kallisti.
-- local filesystem = require("filesystem")

local function disk_finder(fn)
local tmpfn = nil
local ptrmode = nil
local f = nil

return function(a, mode)
if tmpfn == nil or not filesystem.exists(tmpfn) then
ptrmode = nil
end
if ptrmode ~= mode then
if f ~= nil then pcall(function() f:close() end) end
tmpfn = nil
for p1 in filesystem.list("/mnt") do
local tfn = "/mnt/" .. p1 .. fn
if filesystem.exists(tfn) then
tmpfn = tfn
end
end
if tmpfn ~= nil then
f = io.open(tmpfn, mode)
disk_init_data(f, a)
f:seek("set", 0)
ptrmode = mode
else
f = nil
disk_init_data(nil, a)
end
end
return f
end
end

-- Lunatic86 will currently boot from the first floppy disk drive.
-- Function: disk_init(filename or disk_finder, drive ID)
-- 0x00 is A:, 0x01 is B:, 0x80 and later are hard drives.
-- disk_finder(filename) will use OpenComputers to find
-- /mnt/.../filename dynamically and use it as a floppy disk,
-- potentially allowing for some degree of hotswapping.

-- disk_init(disk_finder("disk.img"), 0x00)

-- Svarog86 FreeDOS (seems to work pretty well)
disk_init("images/sv86-1440.img", 0x00)

-- To create a hard disk drive, you probably want to make one
-- in a decent emulator such as Fake86 first.
-- disk_init("images/hdd.img", 0x80)

+ 302
- 0
disks.lua View File

@@ -0,0 +1,302 @@
local drives = {}

RAM[0x475] = 0

local function disk_init_data(fptr, d)
local fsize = 0
if fptr ~= nil then
fsize = fptr:seek("end")
fptr:seek("set", 0)
end

emu_debug(2, string.format("fsize %02X == %d", d.id, fsize))
d.inserted = true
if fsize == 0 then
d.inserted = false
d.sector_size = 0
d.sectors = 0
d.heads = 0
d.cylinders = 0
elseif d.floppy then
d.sector_size = 512
if fsize >= 1474560 then
d.sectors = 18
d.heads = 2
d.cylinders = 80
elseif fsize >= 737280 then
d.sectors = 9
d.heads = 2
d.cylinders = 80
elseif fsize >= (360*1024) then
d.sectors = 9
d.heads = 2
d.cylinders = 40
elseif fsize >= (320*1024) then
d.sectors = 8
d.heads = 2
d.cylinders = 40
elseif fsize >= (180*1024) then
d.sectors = 9
d.heads = 1
d.cylinders = 40
elseif fsize >= (160*1024) then
d.sectors = 8
d.heads = 1
d.cylinders = 40
else
error("unknown fsize: " .. fsize)
end
else
d.sector_size = 512
d.sectors = 63
d.heads = 16
d.cylinders = math.floor(fsize / (d.sector_size*d.sectors*d.heads))
if d.cylinders <= 0 then
error("unknown fsize: " .. fsize)
end
end
emu_debug(2, string.format("disks: added %d x %d x %d x %d @ %02X", d.cylinders, d.heads, d.sectors, d.sector_size, d.id))

-- configure table
local tba = 0xF2000 + d.id*16
if d.id == 0x80 then
RAM:w16(0x0104, tba & 0xFFFF)
RAM:w16(0x0106, 0xF000)
elseif d.id == 0x81 then
RAM:w16(0x0118, tba & 0xFFFF)
RAM:w16(0x011A, 0xF000)
elseif d.id == 0x00 then
RAM:w16(0x0078, tba & 0xFFFF)
RAM:w16(0x007A, 0xF000)
end
if d.floppy then
RAM[tba] = 0xF0
RAM[tba + 1] = 0x00
RAM[tba + 2] = 0x00
RAM[tba + 3] = math.ceil(d.sector_size / 128) - 1
RAM[tba + 4] = d.sectors
RAM[tba + 5] = 0
RAM[tba + 6] = 0
RAM[tba + 7] = 0
RAM[tba + 8] = 0xF6
RAM[tba + 9] = 0
RAM[tba + 10] = 0
else
RAM:w16(tba, d.cylinders)
RAM[tba + 2] = d.heads
RAM:w16(tba + 3, 0)
RAM:w16(tba + 5, 0)
RAM[tba + 7] = 0
RAM[tba + 8] = 0xC0
if d.heads > 8 then
RAM[tba + 8] = RAM[tba + 8] | 0x08
end
RAM[tba + 9] = 0
RAM[tba + 10] = 0
RAM[tba + 11] = 0
RAM:w16(tba + 12, 0)
RAM[tba + 14] = d.sectors
end
end

function disk_init(fn, id)
local d = {}
d.id = id
local is_floppy = id < 0x80
d.floppy = is_floppy

if type(fn) == "function" then
d.ptr = fn
else
local ptrmode = nil
local f = nil
d.ptr = function(a, mode)
if ptrmode ~= mode then
if f ~= nil then f:close() end
f = io.open(fn, mode)
disk_init_data(f, a)
f:seek("set", 0)
ptrmode = mode
end
return f
end
end

drives[id] = d
if d.id >= 0x80 then
RAM[0x475] = RAM[0x475] + 1
end
end

function disk_has(id)
return drives[id] ~= nil
end

local last_status = 0x00

local function ret_status(v)
if v ~= nil then
emu_debug(2, "disks: setting status to " .. v)
last_status = v & 0xFF
end
CPU["regs"][1] = (CPU["regs"][1] & 0xFF) | (last_status << 8)
cpu_write_flag(0, last_status ~= 0)
end

cpu_register_interrupt_handler(0x13, function(ax,ah,al)
if ah == 0x00 then
emu_debug(1, "disks: disk system reset")
ret_status(0)
return true
elseif ah == 0x01 then
ret_status(nil)
return true
elseif ah == 0x02 then
local cx = CPU["regs"][2]
local dx = CPU["regs"][3]
local bx = CPU["regs"][4]
local sector = (cx & 0x3F)
local cylinder = (cx >> 8)
local head = (dx >> 8)
local drive = (dx & 0xFF)
if drive >= 0x80 then
cylinder = cylinder | ((cx & 0xC0) << 2)
end
local d = drives[drive]
if not d or d:ptr("rb") == nil then
ret_status(1)
return true
else
d:ptr("rb")
if sector == 0 or sector > d.sectors or head >= d.heads or cylinder >= d.cylinders then
ret_status(4)
emu_debug(1, string.format("disks: out of bounds - %02X c=%d h=%d s=%d", drive, cylinder, head, sector))
return true
end
end
local pos = cylinder
pos = pos * d.heads + head
pos = pos * d.sectors + (sector - 1)
pos = pos * d.sector_size
CPU["regs"][1] = al -- AH = 0 (OK), AL = sectors transferred
d:ptr("rb"):seek("set", pos)
local count = al * d.sector_size
local data = d:ptr("rb"):read(count)
for i=0,count-1 do
RAM[seg(SEG_ES, bx + i)] = data:byte(i+1,i+2)
end
emu_debug(1, string.format("disks: read %d bytes from %02X %d to %04X %04X", count, drive, pos, CPU["segments"][SEG_ES+1], bx))
ret_status(0)
return true
elseif ah == 0x03 then
local cx = CPU["regs"][2]
local dx = CPU["regs"][3]
local bx = CPU["regs"][4]
local sector = (cx & 0x3F)
local cylinder = (cx >> 8)
local head = (dx >> 8)
local drive = (dx & 0xFF)
if drive >= 0x80 then
cylinder = cylinder | ((cx & 0xC0) << 2)
end
local d = drives[drive]
if not d or d:ptr("ab") == nil then
ret_status(1)
return true
else
d:ptr("ab")
if sector == 0 or sector > d.sectors or head >= d.heads or cylinder >= d.cylinders then
ret_status(4)
emu_debug(1, string.format("disks: out of bounds - %02X c=%d h=%d s=%d", drive, cylinder, head, sector))
return true
end
end
local pos = cylinder
pos = pos * d.heads + head
pos = pos * d.sectors + (sector - 1)
pos = pos * d.sector_size
CPU["regs"][1] = al -- AH = 0 (OK), AL = sectors transferred
d:ptr("ab"):seek("set", pos)
local count = al * d.sector_size
for i=0,count-1 do
d:ptr("ab"):write(string.char(RAM[seg(SEG_ES, bx + i)]))
end
emu_debug(1, string.format("disks: wrote %d bytes from %02X %d to %04X %04X", count, drive, pos, CPU["segments"][SEG_ES+1], bx))
ret_status(0)
return true
elseif ah == 0x04 then -- verify
local drive = CPU["regs"][3] & 0xFF
emu_debug(1, string.format("disks: verify drive %02X", drive))
ret_status(0)
return true
elseif ah == 0x08 then -- get drive parameters
local drive = CPU["regs"][3] & 0xFF
local d = drives[drive]
if not d or d:ptr("rb") == nil then
ret_status(1)
else
d:ptr("rb") -- init disk data
local maxc = d.cylinders - 1
local drives = RAM[0x475]
if d.floppy then drives = ((RAM[0x410] >> 6) & 3) + 1 end
CPU["regs"][2] = ((maxc & 0xFF) << 8) | (d.sectors & 0x3F) | ((maxc & 0x300) >> 2) -- CX = cylinder number | sector number
CPU["regs"][3] = ((d.heads - 1) << 8) | drives
CPU["regs"][8] = 2000 + (drive*16)
CPU["segments"][SEG_ES+1] = 0xF000 -- ES:DI - hdpt ptr
if d.floppy then
if d.sectors == 18 then
CPU["regs"][4] = (CPU["regs"][4] & 0xFF00) | 4
else
CPU["regs"][4] = (CPU["regs"][4] & 0xFF00) | 3
end
else
CPU["regs"][4] = (CPU["regs"][4] & 0xFF00)
end
ret_status(0)
end
emu_debug(1, string.format("disks: get drive parameters %02X %04X", drive, CPU["regs"][1]))
return true
elseif ah == 0x15 then -- get disk type
local drive = CPU["regs"][3] & 0xFF
local d = drives[drive]
local code = 0
if d and d:ptr("rb") ~= nil then
if d.floppy then code = 1
else code = 3 end
end
cpu_clear_flag(0) -- clear carry
CPU["regs"][1] = (code << 8) | (CPU["regs"][1] & 0xFF) -- AH = drive code
emu_debug(1, string.format("disks: get disk type %02X", drive))
return true
elseif ah == 0x18 then -- set media type for format
-- TODO
local drive = CPU["regs"][3] & 0xFF
local code = 0x80
if drives[drive] then code = 0 end
cpu_clear_flag(0) -- clear carry
CPU["regs"][1] = (code << 8) | (CPU["regs"][1] & 0xFF) -- AH = drive code
emu_debug(1, string.format("disks: set media type %02X", drive))
return true
elseif ah == 0x41 then -- check extensions present
ret_status(1)
return true
else
return false
end
end)

function disk_boot(id)
local f = drives[id]:ptr("rb")
f:seek("set", 0)
local bootsector = f:read(512)
for i=0,511 do
RAM[0x7c00 + i] = bootsector:byte(i+1,i+2)
end
cpu_set_ip(0x0000, 0x7C00)
CPU["regs"][3] = 0x0000 | id
CPU["regs"][5] = 0x8000
end

dofile("config_disks.lua")

disk_boot(0x00)

+ 2032
- 0
emup.lua
File diff suppressed because it is too large
View File


+ 41
- 0
kbdmaps.lua View File

@@ -0,0 +1,41 @@
map_char_to_key = {}
map_char_to_key[258] = 0x40 -- down
map_char_to_key[260] = 0x4B -- left
map_char_to_key[259] = 0x48 -- up
map_char_to_key[261] = 0x4D -- right
map_char_to_key[13] = 0x1C -- enter
map_char_to_key[0] = 0
map_char_to_key[0x1C] = 1 -- esc
for i=1,9 do map_char_to_key[i + 48] = i + 1 end
local chrs = "!@#$%^&*()"
for i=1,#chrs do map_char_to_key[string.byte(chrs, i, i)] = i + 1 end
map_char_to_key[48] = 11 -- numbers
map_char_to_key[45] = 12 -- -
map_char_to_key[95] = 12 -- _
map_char_to_key[61] = 13 -- =
map_char_to_key[43] = 12 -- +
map_char_to_key[8] = 14 -- backspace
map_char_to_key[9] = 15 -- tab
local chrs = "qwertyuiop[]"
for i=1,#chrs do map_char_to_key[string.byte(chrs,i,i)] = 15 + i end
local chrs = "QWERTYUIOP{}"
for i=1,#chrs do map_char_to_key[string.byte(chrs,i,i)] = 15 + i end
map_char_to_key[13] = 28 -- enter
-- 29?
local chrs = "asdfghjkl;'"
for i=1,#chrs do map_char_to_key[string.byte(chrs,i,i)] = 29 + i end
local chrs = "ASDFGHJKL:\""
for i=1,#chrs do map_char_to_key[string.byte(chrs,i,i)] = 29 + i end
map_char_to_key[96] = 41 -- `
map_char_to_key[126] = 41 -- ~
-- 42?
map_char_to_key[92] = 43 -- \
map_char_to_key[124] = 43 -- |
local chrs = "zxcvbnm,./"
for i=1,#chrs do map_char_to_key[string.byte(chrs,i,i)] = 43 + i end
local chrs = "ZXCVBNM<>?"
for i=1,#chrs do map_char_to_key[string.byte(chrs,i,i)] = 43 + i end
-- 54?
map_char_to_key[42] = 55 -- *
-- 56?
map_char_to_key[32] = 57 -- space

+ 160
- 0
keyboard.lua View File

@@ -0,0 +1,160 @@
-- TODO: The keyboard code is a massive hack which in no way reflects real hardware.

-- joysticks (for now)
cpu_port_set(0x201, function(cond,val)
if not val then
return 0
end
end)

local charqueue = {}
local kbd_data_queue = {}

local function populate_bitmask(kz)
local keys = 0
for i,k in ipairs(kz) do
if k >= 0 and platform_key_down(k) then
keys = keys | (1 << (i - 1))
end
end
return keys
end

function keyboard_update()
RAM[0x417] = populate_bitmask({0x36, 0x2A, 0x1D, 0x38, 0x46, -1, 0x3A, 0x52})
RAM[0x418] = populate_bitmask({-1, -1, -1, -1, -1, -1, -1, -1})
end

function kbd_send_ibm(code, chr)
while #kbd_data_queue > 0 do table.remove(kbd_data_queue, 1) end
if code <= 0x7F then
table.insert(charqueue, {code, chr or 0})
end
table.insert(kbd_data_queue, code)
emu_debug(2, string.format("kbd_send_ibm: %02X", code))
if pic_interrupt_enabled(PIC_INTERRUPT_KEYBOARD) then
cpu_emit_interrupt(9, false)
end
end

RAM[0x471] = 0 -- break key check
RAM[0x496] = 0 -- keyboard mode/type
RAM[0x497] = 0 -- keyboard LED flags
RAM[0x417] = 0 -- keyboard flags 1
RAM[0x418] = 0 -- keyboard flags 2

local kbd_port_b = 0x03
local kbd_spkr_latch = 0

function kbd_get_spkr_latch()
local v = kbd_spkr_latch
kbd_spkr_latch = 0
return v
end

function kbd_get_port_b()
return kbd_port_b
end

local kbd_status = 0x10
local kbd_a2 = 0

-- keyboard I/O
cpu_port_set(0x60, function(cond,val)
if not val then
emu_debug(1, string.format("kbd 0060: read"))
if #kbd_data_queue > 0 then
local v = kbd_data_queue[1]
table.remove(kbd_data_queue, 1)
if type(v) == "table" then return v[1] end
return v
end
return 0x00
else
kbd_a2 = 0
emu_debug(1, string.format("kbd 0060: received %02X", val))
if val == 5 then -- TODO: What is this?
kbd_data_queue = {}
table.insert(kbd_data_queue, 0x00)
table.insert(kbd_data_queue, 0x00)
table.insert(kbd_data_queue, 0x00)
end
end
end)

cpu_port_set(0x61, function(cond,val)
if not val then
emu_debug(1, "kbd port b read")
return kbd_port_b
else
emu_debug(1, "kbd port b write " .. val)
kbd_port_b = val
kbd_spkr_latch = kbd_spkr_latch | val
end
end)

cpu_port_set(0x64, function(cond,val)
if not val then
emu_debug(1, string.format("kbd 0064: read"))
local v = kbd_status | (kbd_a2 << 3)
if #kbd_data_queue > 0 then
kbd_status = kbd_status ~ 3
else
kbd_status = kbd_status & (0xFC)
end
return v
else
kbd_a2 = 1
emu_debug(1, string.format("kbd 0064: received %02X", val))
end
end)

cpu_register_interrupt_handler(0x16, function(ax,ah,al)
if (ah == 0x00) then
local bios, ascii = nil, nil
local dd = nil
if type(charqueue[1]) == "table" then
dd = charqueue[1]
table.remove(charqueue, 1)
end

if dd ~= nil then
bios = dd[1]
ascii = dd[2]
end

if bios == nil then
cpu_set_flag(6)
return "block"
else
cpu_clear_flag(6)
CPU["regs"][1] = ((bios & 0xFF) << 8) | (ascii & 0xFF)
return true
end
elseif (ah == 0x01) then
-- check for keystroke
local ascii, bios = nil, nil
if (#charqueue > 0) and type(charqueue[1]) == "table" then
ascii = charqueue[1][2]
bios = charqueue[1][1]
end

if bios == nil then
cpu_set_flag(6)
return true
else
cpu_clear_flag(6)
CPU["regs"][1] = ((bios & 0xFF) << 8) | (ascii & 0xFF)
return true
end
elseif (ah == 0x02) then
-- query shift flags
if ah == 0x12 then
ah = RAM[0x418]
end
al = RAM[0x417]
CPU["regs"][1] = (ah << 8) | al
return true
end
end)


+ 62
- 0
pic.lua View File

@@ -0,0 +1,62 @@
-- 8259 PIC
-- TODO: Not actually emulated.

PIC_INTERRUPT_PRINTER = 7
PIC_INTERRUPT_FDD = 6
PIC_INTERRUPT_HDD = 5
PIC_INTERRUPT_COM1 = 4
PIC_INTERRUPT_COM2 = 3
PIC_INTERRUPT_VIDEO = 2
PIC_INTERRUPT_KEYBOARD = 1
PIC_INTERRUPT_MOUSE = 1
PIC_INTERRUPT_RTC = 1
PIC_INTERRUPT_TIMER = 0

local int_mask = 0x00
local pic_irr = 0x00
local pic_isr = 0x00

function pic_interrupt_enabled(i)
return true
-- return (int_mask & (1 << i)) == 0
end

--function pic_interrupt_sent(i)
-- pic_isr = pic_isr | (1 << i)
--end

local next_20 = 0x00

pic_isr = 0xFF

cpu_port_set(0x20, function(cond, val)
if val then next_20 = val else
if next_20 == 0x0A then
return pic_irr
elseif next_20 == 0x0B then
pic_isr = pic_isr ~ 0xFF
return pic_isr
elseif next_20 == 0x20 then
-- TODO
return 0
else
-- unknown
return 0xFF
end
end
end)

local int_22 = 0
local int_23 = 0

cpu_port_set(0x21, function(cond, val)
if not val then return int_mask else int_mask = val end
end)

cpu_port_set(0x22, function(cond, val)
if not val then return int_22 else int_22 = val end
end)

cpu_port_set(0x23, function(cond, val)
if not val then return int_23 else int_23 = val end
end)

+ 123
- 0
pit.lua View File

@@ -0,0 +1,123 @@
-- 8253 PIT
-- TODO: Actually emulate it. Tough, as we only get 20 "ticks" a second on OpenComputers.
-- Maybe it's not worth to accurately emulate it?

pit_osc_freq = 1.193182

local channels = {nil, nil, nil}

function pit_channel(c)
return channels[c]
end

local function pit_init(c,first)
channels[c] = {
mode=3,
addr_mode=3,
reload=0xFFFF,
reload_set_lo=false,
reload_set_hi=false,
paused=false,
count=0
}
end

-- THIS IS REALLY INACCURATE OKAY
-- but that is fine as we're not using it right now anyway
function pit_tick(last_t, curr_t)
local osc_count = math.floor((curr_t - last_t) * 1000000 * pit_osc_freq)
for c=1,3 do
local trig=0
local ch=channels[c]
local ch_ready=(not ch.paused) and ch.reload_set_lo and ch.reload_set_hi
if (ch.mode == 0 or ch.mode == 4) and ch_ready then
if ch.count == 0 then ch.count = ch.reload end
ch.count = ch.count - osc_count
if ch.count < 1 then
trig = 1
ch.count = 0
ch.paused = true
end
elseif (ch.mode == 2 or ch.mode == 3) and ch_ready then
ch.count = ch.count - osc_count
while ch.count < 0 do
ch.count = ch.count + ch.reload
trig = trig + 1
end
end
for i=1,trig do
if c == 1 and pic_interrupt_enabled(PIC_INTERRUPT_TIMER) then
cpu_emit_interrupt(0x08, false)
end
end
end
end

local access_lohi = false

local function pit_data(n) return function(cond, val)
local ch = channels[n]
if not val then
emu_debug(2, "PIT read data " .. n)
if ch.addr_mode == 3 then
access_lohi = not access_lohi
if access_lohi then
return ch.count & 0xFF
else
return (ch.count >> 8) & 0xFF
end
elseif ch.addr_mode == 1 then
return ch.count & 0xFF
elseif ch.addr_mode == 2 then
return (ch.count >> 8) & 0xFF
elseif ch.addr_mode == 0 then
return 0x00 -- TODO
end
else
emu_debug(2, "PIT write data " .. n)
if ch.addr_mode == 3 then
access_lohi = not access_lohi
if access_lohi then
ch.reload = (ch.reload & 0xFF00) | val
ch.reload_set_lo = true
ch.reload_set_hi = false
else
ch.reload = (ch.reload & 0xFF) | (val << 8)
ch.reload_set_hi = true
end
elseif ch.addr_mode == 1 then
ch.reload = (ch.reload & 0xFF00) | val
ch.reload_set_lo = true
elseif ch.addr_mode == 2 then
ch.reload = (ch.reload & 0xFF) | (val << 8)
ch.reload_set_hi = true
elseif ch.addr_mode == 0 then
-- TODO
end
end
end end

cpu_port_set(0x40, pit_data(1))
cpu_port_set(0x41, pit_data(2))
cpu_port_set(0x42, pit_data(3))
cpu_port_set(0x43, function(cond, val)
if val then
emu_debug(2, "PIT write control " .. val)
local c = (val >> 6) + 1
if c < 4 then
pit_init(c,false)
channels[c].mode = (val >> 1) & 0x07
channels[c].addr_mode = (val >> 4) & 0x03
channels[c].paused = false
access_lohi = false
end
else
emu_debug(2, "PIT read control")
return 0xFF
end
end)

pit_init(1,true)
pit_init(2,true)
pit_init(3,true)


+ 113
- 0
platform.lua View File

@@ -0,0 +1,113 @@
os.setlocale('')

local curses = require "curses"
local cp437_trans = require("table_cp437")
dofile("kbdmaps.lua")

function platform_sleep(t)
-- for what pvrpose
os.execute("sleep " .. tonumber(t))
end

local stdscr = curses.initscr()
curses.cbreak()
curses.echo(false)
curses.nl(false)
stdscr:clear()
stdscr:nodelay(true)
stdscr:scrollok(true)
stdscr:keypad(true)

function platform_beep(freq)
emu_debug(2, "BEEP " .. freq)
end

function platform_key_down(v)
return false
end

function emu_debug(level, s, tb)
if level >= 1 then
io.stderr:write(s .. "\n")
if tb then debug.traceback() end
io.stderr:flush()
end
end

local queued_up = {}

-- non-blocking, returns (ascii char, bios scan code) or nil on none
function platform_getc()
local c = stdscr:getch()
if c == nil then return nil end
if type(c) == "string" then c = string.byte() end
emu_debug(2, string.format("getc %d", c))
if map_char_to_key[c] then
if c >= 0 and c < 128 then return c,map_char_to_key[c]
else return 0,map_char_to_key[c] end
end
return nil
end

function platform_error(msg)
curses.endwin()
print(msg)
print(debug.traceback())
end

local function platform_kbd_tick()
local getmore = true
while getmore do
if #queued_up > 0 then
kbd_send_ibm(queued_up[1][1], queued_up[1][2])
table.remove(queued_up, 1)
end

local ch, code = platform_getc()
if ch ~= nil then
kbd_send_ibm(code, ch)
-- queued_up[#queued_up+1] = {code | 0x80, ch}
else getmore = false end
end
end

function platform_render_cga_mono(vram, addr)
platform_kbd_tick()
end

function platform_render_mcga_13h(vram, addr)
platform_kbd_tick()
end

function platform_render_pcjr_160(vram, addr)
platform_kbd_tick()
end

function platform_render_pcjr_320(vram, addr)
platform_kbd_tick()
end

function platform_render_text(vram, addr, width, height, pitch)
platform_kbd_tick()

local dlines = video_pop_dirty_lines()
for y,v in pairs(dlines) do
local base = addr + (y * pitch)
for x=0,width-1 do
local chr = cp437_trans[vram[base + x*2] or 0]
local atr = vram[base + x*2 + 1] or 0
stdscr:mvaddstr(y, x, utf8.char(chr))
end
end
stdscr:refresh()
end

xpcall(function()
dofile("./emu.lua")
end, function(err)
curses.endwin()
print("Caught an error: " .. err)
print(debug.traceback())
end)

curses.endwin()

+ 493
- 0
platform_oc.lua View File

@@ -0,0 +1,493 @@
local component = require("component")
local event = require("event")
local unicode = require("unicode")
local keyboard = require("keyboard")
local computer = require("computer")
local gpu = component.gpu
local cp437_trans = require("table_cp437")
local oc_palette = require("table_ocpalette")
dofile("kbdmaps.lua")

local beeper = component.beep
--local beeper = nil

local qdr = {}
for i=0,255 do
local dat = (i & 0x01) << 7
dat = dat | (i & 0x02) >> 1 << 6
dat = dat | (i & 0x04) >> 2 << 5
dat = dat | (i & 0x08) >> 3 << 2
dat = dat | (i & 0x10) >> 4 << 4
dat = dat | (i & 0x20) >> 5 << 1
dat = dat | (i & 0x40) >> 6 << 3
dat = dat | (i & 0x80) >> 7
qdr[i + 1] = unicode.char(0x2800 | dat)
end

function emu_debug(s)
return false
end

function platform_beep(freq)
freq = math.floor(freq)
if freq < 20 then freq = 20
elseif freq > 2000 then freq = 2000 end
if beeper then beeper.beep({[freq]=0.05}) end
end

function platform_sleep(t)
os.sleep(t)
end

function platform_key_down(code)
return keyboard.isKeyDown(code)
end

event.listen("redstone_changed", function(name, addr, side, oldV, newV)
if oldV < newV and newV >= 8 then
kbd_send_ibm(0x01, 0x1B)
end
end)

local function oc_code_transform(code, char)
if code == 0x1C then char = 13 end
if code >= 0xC0 and code <= 0xDF then code = code & 0x7F end
if code >= 0x80 then
-- fallback
code = map_char_to_key[char] or code
end
return code, char
end

local kd_matrix = {}

event.listen("key_down", function(name, addr, char, code, player)
code, char = oc_code_transform(code, char)
if code >= 0 and code <= 0x7F and not kd_matrix[code] then
kd_matrix[code] = true
kbd_send_ibm(code, char)
end
end)

event.listen("key_up", function(name, addr, char, code, player)
code, char = oc_code_transform(code, char)
if code >= 0 and code <= 0x7F then
kd_matrix[code] = nil
kbd_send_ibm(0x80 | code, char)
end
end)

function platform_error(msg)
error(msg)
end

local function dstrings_add(dstrings, atr, x, y, str)
if dstrings[atr] then
table.insert(dstrings[atr], {x, y, table.concat(str)})
else
dstrings[atr] = {{x, y, table.concat(str)}}
end
end

local function dstrings_draw(dstrings, pal)
local dstrkeys = {}
for k in pairs(dstrings) do table.insert(dstrkeys, k) end
table.sort(dstrkeys)

local lastBG = -1
local lastFG = -1

for _,atr in pairs(dstrkeys) do
local arr = dstrings[atr]
if pal then
local tbg = (atr & 0xFF00) >> 8
local tfg = atr & 0x00FF
if lastBG ~= tbg then
gpu.setBackground(oc_palette[tbg])
lastBG = tbg
end
if lastFG ~= tfg then
gpu.setForeground(oc_palette[tfg])
lastFG = tfg
end
else
local tbg = (atr & 0xF0) >> 4
local tfg = atr & 0x0F
if lastBG ~= tbg then
gpu.setBackground(pc_16_colors[1 + tbg])
lastBG = tbg
end
if lastFG ~= tfg then
gpu.setForeground(pc_16_colors[1 + tfg])
lastFG = tfg
end
end

for i,dat in ipairs(arr) do
gpu.set(dat[1], dat[2], dat[3], false)
end
end
end

local function gpu_set_res_diff(width, height)
local rw, rh = gpu.getResolution()
if rw ~= width or rh ~= height then
gpu.setResolution(width, height)
end
end


-- Takes four pixels and turns them into two.
--local plat_cga_mono_avg = {
-- 0, 1, 0, 1,
-- 2, 3, 2, 3,
-- 0, 1, 0, 1,
-- 2, 3, 2, 3
--} Naive
local plat_cga_mono_avg = {
0, 1, 1, 1,
2, 1, 2, 1,
2, 1, 2, 1,
2, 2, 2, 3
} -- Less naive?

-- 00..0F for each
local function plat_cga_chr(v1, v2, v3, v4)
return qdr[1 +
( (plat_cga_mono_avg[v4 + 1])
| (plat_cga_mono_avg[v3 + 1] << 2)
| (plat_cga_mono_avg[v2 + 1] << 4)
| (plat_cga_mono_avg[v1 + 1] << 6) )
]
end

local function colorDistSq(rgb1, rgb2)
if rgb1 == rgb2 then return 0 end

local r1 = (rgb1 >> 16) & 0xFF
local g1 = (rgb1 >> 8) & 0xFF
local b1 = (rgb1) & 0xFF
local r2 = (rgb2 >> 16) & 0xFF
local g2 = (rgb2 >> 8) & 0xFF
local b2 = (rgb2) & 0xFF
local rs = (r1 - r2) * (r1 - r2)
local gs = (g1 - g2) * (g1 - g2)
local bs = (b1 - b2) * (b1 - b2)
local rAvg = math.floor((r1 + r2) / 2)

return (((512 + rAvg) * rs) >> 8) + (4 * gs) + (((767 - rAvg) * bs) >> 8)
end

local function round(v)
return math.floor(v + 0.5)
end

local function getOCPalEntry(rgb)
local r = (rgb >> 16) & 0xFF
local g = (rgb >> 8) & 0xFF
local b = (rgb) & 0xFF
if r == g and g == b then
local i = round(g * 16.0 / 255.0)
if i <= 0 then return 16 elseif i >= 16 then return 255 else return i - 1 end
else
return 16 + (round(r * 5.0 / 255.0) * 40) + (round(g * 7.0 / 255.0) * 5) + round(b * 4.0 / 255.0)
end
end

-- p8 = bottom right, p7 = bottom left, ...
-- returns: bg, fg, chr
local AVG_CHR_2x4 = 0
local AVG_CHR_1x4 = 1
local AVG_CHR_2x2 = 2

local function plat_avg_chr(pixels, pal, avg_chr_mode, empty_color)
-- identify most common colors
local pixelCount = {}
local pixelColors = 0
for i=1,#pixels do
if pixelCount[pixels[i]] == nil then pixelColors = pixelColors + 1 end
pixelCount[pixels[i]] = (pixelCount[pixels[i]] or 0) + 1
end
local bg, fg
if pixelColors == 1 then
return pixels[1], empty_color or 0, 0
elseif pixelColors == 2 then
bg = pixels[1]
fg = -1
for k,v in pairs(pixelCount) do
if k ~= bg then
fg = k
break
end
end
else
local bgc = -1
local fgc = -1
for k,v in pairs(pixelCount) do
if v > bgc then
bg = k
bgc = v
end
end
for k,v in pairs(pixelCount) do
if k ~= bg then
local contrast = colorDistSq(pal[bg], pal[k]) * v
if contrast > fgc then
fg = k
fgc = contrast
end
end
end
end
local chr = 0
local pbg = pal[bg]
local pfg = pal[fg]
if avg_chr_mode == AVG_CHR_2x4 then
for i=1,8 do
local p = pal[pixels[i]]
if colorDistSq(pfg, p) < colorDistSq(pbg, p) then
chr = chr | (0x80 >> (i - 1))
end
end
elseif avg_chr_mode == AVG_CHR_1x4 then
for i=1,4 do
local p = pal[pixels[i]]
if colorDistSq(pfg, p) < colorDistSq(pbg, p) then
chr = chr | (0xC0 >> ((i - 1) << 1))
end
end
elseif avg_chr_mode == AVG_CHR_2x2 then
local ash = {0, 1, 4, 5}
for i=1,4 do
local p = pal[pixels[i]]
if colorDistSq(pfg, p) < colorDistSq(pbg, p) then
chr = chr | (0xA0 >> ash[i])
end
end
end
return bg, fg, chr
end

local function iter_dstrings(dlines, dstrings, func)
for y,dline in pairs(dlines) do
local lastAtr = -1
local lastX = dline[1]
local str = {}
for x=dline[1],dline[2] do
local atr, chr = func(x, y)
if lastAtr ~= atr then
if #str > 0 then
dstrings_add(dstrings, lastAtr, lastX + 1, y + 1, str)
end

lastAtr = atr
lastX = x
str = {chr}
else
str[#str + 1] = chr
end
end
if #str > 0 then
dstrings_add(dstrings, lastAtr, lastX + 1, y + 1, str)
end
end
end

local function dlines_scale(dlines, amt)
for y,dline in pairs(dlines) do
dline[1] = dline[1]*amt
dline[2] = dline[2]*amt+(amt-1)
end
end

function platform_render_mcga_13h(vram, addr)
gpu_set_res_diff(160, 50)
local dlines = video_pop_dirty_lines()
local dstrings = {}

iter_dstrings(dlines, dstrings, function(x, y)
local base = addr + y*1280
local bg, fg, chr = plat_avg_chr({
getOCPalEntry(video_vga_get_palette(vram[base + x*2] or 0)),
getOCPalEntry(video_vga_get_palette(vram[base + x*2 + 1] or 0)),
getOCPalEntry(video_vga_get_palette(vram[base + x*2 + 320] or 0)),
getOCPalEntry(video_vga_get_palette(vram[base + x*2 + 321] or 0)),
getOCPalEntry(video_vga_get_palette(vram[base + x*2 + 640] or 0)),
getOCPalEntry(video_vga_get_palette(vram[base + x*2 + 641] or 0)),
getOCPalEntry(video_vga_get_palette(vram[base + x*2 + 960] or 0)),
getOCPalEntry(video_vga_get_palette(vram[base + x*2 + 961] or 0))
}, oc_palette, AVG_CHR_2x4)
local atr = (bg << 8) | fg
return atr, qdr[1 + chr]
end)

dstrings_draw(dstrings, true)
end

function platform_render_pcjr_160(vram, addr)
gpu_set_res_diff(160, 50)
local dlines = video_pop_dirty_lines()
local dstrings = {}

dlines_scale(dlines, 2)
iter_dstrings(dlines, dstrings, function(x, y)
local base = addr + x + y*160 -- 4/2 lines
local shift = 4
if (x & 1) == 1 then shift = 0 end
local bg, fg, chr = plat_avg_chr({
(((vram[base] or 0) >> shift) & 0x0F) + 1,
(((vram[base + 0x2000] or 0) >> shift) & 0x0F) + 1,
(((vram[base + 80] or 0) >> shift) & 0x0F) + 1,
(((vram[base + 80 + 0x2000] or 0) >> shift) & 0x0F) + 1
}, pc_16_colors, AVG_CHR_1x4, 1)
local atr = ((bg - 1) << 4) | (fg - 1)
return atr, qdr[1 + chr]
end)

dstrings_draw(dstrings, false)
end

function platform_render_pcjr_320(vram, addr)
gpu_set_res_diff(160, 50)
local dlines = video_pop_dirty_lines()
local dstrings = {}

iter_dstrings(dlines, dstrings, function(x, y)
local base = addr + x + y*160 -- 4/4 lines
local bg, fg, chr = plat_avg_chr({
(((vram[base] or 0) >> 4) & 0x0F) + 1,
(((vram[base] or 0) ) & 0x0F) + 1,
(((vram[base+0x2000] or 0) >> 4) & 0x0F) + 1,
(((vram[base+0x2000] or 0) ) & 0x0F) + 1,
(((vram[base+0x4000] or 0) >> 4) & 0x0F) + 1,
(((vram[base+0x4000] or 0) ) & 0x0F) + 1,
(((vram[base+0x6000] or 0) >> 4) & 0x0F) + 1,
(((vram[base+0x6000] or 0) ) & 0x0F) + 1
}, pc_16_colors, AVG_CHR_2x4, 1)
local atr = ((bg - 1) << 4) | (fg - 1)
return atr, qdr[1 + chr]
end)

dstrings_draw(dstrings, false)
end

local cga_col_pals = {}
cga_col_pals[0] = {0, 11, 13, 15}
cga_col_pals[1] = {0, 10, 12, 14}
cga_col_pals[2] = {0, 11, 12, 15}
cga_col_pals[3] = {0, 3, 5, 7}
cga_col_pals[4] = {0, 2, 4, 6}
cga_col_pals[5] = {0, 3, 4, 7}

local function plat_avg_chr_cga(c1, c2, c3, c4, sh, cgp)
local cga_col_pal = cga_col_pals[cgp]
local bg, fg, ch = plat_avg_chr({
cga_col_pal[((c1 >> (sh+2)) & 3) + 1] + 1,
cga_col_pal[((c1 >> sh) & 3) + 1] + 1,
cga_col_pal[((c2 >> (sh+2)) & 3) + 1] + 1,
cga_col_pal[((c2 >> sh) & 3) + 1] + 1,
cga_col_pal[((c3 >> (sh+2)) & 3) + 1] + 1,
cga_col_pal[((c3 >> sh) & 3) + 1] + 1,
cga_col_pal[((c4 >> (sh+2)) & 3) + 1] + 1,
cga_col_pal[((c4 >> sh) & 3) + 1] + 1
}, pc_16_colors, AVG_CHR_2x4, 1)
return bg - 1, fg - 1, ch
end

function platform_render_cga_color(vram, addr, mode)
gpu_set_res_diff(160, 50)
local dlines = video_pop_dirty_lines()
local dstrings = {}

local cgp = (((cga_get_palette() >> 4) & 1) * 3)
if mode == 5 then
cgp = cgp + 2
else
cgp = cgp + (cga_get_palette() >> 5) & 1
end
-- cga_col_pals[cgp][1] = (cga_get_palette() & 0x0F)

dlines_scale(dlines, 2)
iter_dstrings(dlines, dstrings, function(x, y)
local base = addr + y*160
local xs = x >> 1
local bg, fg, chr = plat_avg_chr_cga(
vram[base + xs] or 0,
vram[base + xs + 0x2000] or 0,
vram[base + xs + 80] or 0,
vram[base + xs + 0x2000 + 80] or 0,
(4 - ((x & 1) * 4)), cgp
)
local atr = (bg << 4) | fg
return atr, qdr[1 + chr]
end)

dstrings_draw(dstrings, false)
end

function platform_render_cga_mono(vram, addr)
gpu_set_res_diff(160, 50)
gpu.setBackground(pc_16_colors[1])
gpu.setForeground(pc_16_colors[16])
-- gpu.setForeground(pc_16_colors[(cga_get_palette() & 0x0F) + 1])

local dlines = video_pop_dirty_lines()
for y,dline in pairs(dlines) do
local str = {}
-- y = 0..49 -> 0, 4, ... -> jump by two odd scanlines (160 bytes)
local base = addr + y*160
for x=dline[1],dline[2] do
-- left
local c1 = vram[base + x] or 0
local c2 = vram[base + x + 0x2000] or 0
local c3 = vram[base + x + 80] or 0
local c4 = vram[base + x + 0x2000 + 80] or 0
str[#str + 1] = plat_cga_chr(c1 >> 4, c2 >> 4, c3 >> 4, c4 >> 4)
str[#str + 1] = plat_cga_chr(c1 & 0x0F, c2 & 0x0F, c3 & 0x0F, c4 & 0x0F)
end
gpu.set(dline[1] * 2 + 1, y + 1, table.concat(str))
end
end

function platform_render_text(vram, addr, width, height, pitch)
gpu_set_res_diff(width, height)

local dlines = video_pop_dirty_lines()
local dstrings = {}

for y,dline in pairs(dlines) do
local base = addr + (y * pitch)
local lastAtr = -1
local lastX = 0
local str = {}
if dline[2] >= width then dline[2] = width-1 end
for x=dline[1],dline[2] do
local chr = cp437_trans[vram[base + x*2] or 0]
local atr = vram[base + x*2 + 1] or 0
-- TO TASTE
if chr == 0x2593 then
chr = 0x2591
atr = (atr >> 4) | ((atr << 4) & 0xF0)
end

if lastAtr ~= atr then
if #str > 0 then
dstrings_add(dstrings, lastAtr, lastX + 1, y + 1, str)
end

lastAtr = atr
lastX = x
str = {unicode.char(chr)}
else
str[#str + 1] = unicode.char(chr)
end
end
if #str > 0 then
dstrings_add(dstrings, lastAtr, lastX + 1, y + 1, str)
end
end

dstrings_draw(dstrings)
end

dofile("./emu.lua")

+ 131
- 0
rotate.lua View File

@@ -0,0 +1,131 @@
local ROTATE_MODE_ROR = 0
local ROTATE_MODE_ROL = 1
local ROTATE_MODE_RCR = 2
local ROTATE_MODE_RCL = 3

local function cpu_rotate(v2, v1, mode, cf)
opcode = 1
local w = (opcode & 0x01)
local shift = 15
if w == 0 then shift = 7 end

local vr = v2
local of = 0

local shifts = v1
if shifts > 0 then
if mode == ROTATE_MODE_ROR then
while shifts > 0 do
cf = (vr & 0x01)
vr = (vr >> 1) | ((vr & 0x01) << shift)
shifts = shifts - 1
end
of = ((vr >> shift) ~ (vr >> (shift - 1))) & 0x01
elseif mode == ROTATE_MODE_ROL then
while shifts > 0 do
cf = (vr >> shift) & 0x01
vr = ((vr << 1) & ((1 << (shift + 1)) - 1)) | (vr >> shift)
shifts = shifts - 1
end
of = ((vr >> shift) ~ cf) & 0x01
elseif mode == ROTATE_MODE_RCR then
while shifts > 0 do
local newcf = (vr & 0x01)
vr = (vr >> 1) | (cf << shift)
shifts = shifts - 1
cf = newcf
end
of = ((vr >> shift) ~ (vr >> (shift - 1))) & 0x01
elseif mode == ROTATE_MODE_RCL then
while shifts > 0 do
local newcf = (vr >> shift) & 0x01
vr = ((vr << 1) & ((1 << (shift + 1)) - 1)) | cf
shifts = shifts - 1
cf = newcf
end
of = ((vr >> shift) ~ cf) & 0x01
end
end
return (vr & 0xFFFF) | (cf << 16) | (of << 17)
end

local function cpu_rotate_new(v2, v1, mode, cf)
opcode = 1
local w = (opcode & 0x01)
local shift = 15
if w == 0 then shift = 7 end

local vr = v2
local of = 0

local shifts = v1
if shifts > 0 then
if mode == ROTATE_MODE_ROR then
local shiftmask = (1 << shifts) - 1
cf = (vr >> (shifts - 1)) & 0x01
vr = (vr >> shifts) | ((vr & shiftmask) << (shift - shifts + 1))
of = ((vr >> shift) ~ (vr >> (shift - 1))) & 0x01
elseif mode == ROTATE_MODE_ROL then
cf = (vr >> (shift - shifts + 1)) & 0x01
vr = ((vr << shifts) & ((1 << (shift + 1)) - 1)) | (vr >> (shift - shifts + 1))
of = ((vr >> shift) ~ cf) & 0x01
elseif mode == ROTATE_MODE_RCR then
while shifts > 0 do
local newcf = (vr & 0x01)
vr = (vr >> 1) | (cf << shift)
shifts = shifts - 1
cf = newcf
end
of = ((vr >> shift) ~ (vr >> (shift - 1))) & 0x01
elseif mode == ROTATE_MODE_RCL then
while shifts > 0 do
local newcf = (vr >> shift) & 0x01
vr = ((vr << 1) & ((1 << (shift + 1)) - 1)) | cf
shifts = shifts - 1
cf = newcf
end
of = ((vr >> shift) ~ cf) & 0x01
end
end
return (vr & 0xFFFF) | (cf << 16) | (of << 17)
end

for i=0,3 do
print("Testing mode " .. i)
for j=0,15 do
for k=0,65535 do
for l=0,1 do
if cpu_rotate_new(k,j,i,l) ~= cpu_rotate(k,j,i,l) then
print("MISMATCH! " .. k .. "(" .. l .. ") rot " .. j .. " = " .. cpu_rotate_new(k,j,i,l) .. ", should " .. cpu_rotate(k,j,i,l))
end
end
end
end
end

print("Benchmarking...")

local speeds_old={}
local speeds_new={}

for i=0,3 do
speeds_old[i] = os.clock()
for j=0,15 do
for k=0,65535 do
for l=0,1 do
cpu_rotate(k,j,i,l)
end
end
end
speeds_old[i] = os.clock() - speeds_old[i]
speeds_new[i] = os.clock()
for j=0,15 do
for k=0,65535 do
for l=0,1 do
cpu_rotate_new(k,j,i,l)
end
end
end
speeds_new[i] = os.clock() - speeds_new[i]
print("Mode " .. i .. ": old = " .. speeds_old[i] .. ", new = " .. speeds_new[i])
end

+ 1
- 0
startup.lua View File

@@ -0,0 +1 @@
dofile("platform_oc.lua")

+ 258
- 0
table_cp437.lua View File

@@ -0,0 +1,258 @@
return {
[0x00] = 0x0020,
[0x01] = 0x263A,
[0x02] = 0x263B,
[0x03] = 0x2665,
[0x04] = 0x2666,
[0x05] = 0x2663,
[0x06] = 0x2660,
[0x07] = 0x2022,
[0x08] = 0x25D8,
[0x09] = 0x25EF,
[0x0A] = 0x25D9,
[0x0B] = 0x2642,
[0x0C] = 0x2640,
[0x0D] = 0x266A,
[0x0E] = 0x266C,
[0x0F] = 0x263C,
[0x10] = 0x25BA,
[0x11] = 0x25C4,
[0x12] = 0x21A5, -- TODO: should be 2195 (await OpenComputers update)
[0x13] = 0x203C,
[0x14] = 0x00B6,
[0x15] = 0x00A7,
[0x16] = 0x25AC,
[0x17] = 0x21A8,
[0x18] = 0x2191,
[0x19] = 0x2193,
[0x1A] = 0x2192,
[0x1B] = 0x2190,
[0x1C] = 0x2319,
[0x1D] = 0x2194,
[0x1E] = 0x25B2,
[0x1F] = 0x25BC,
[0x20] = 0x0020,
[0x21] = 0x0021,
[0x22] = 0x0022,
[0x23] = 0x0023,
[0x24] = 0x0024,
[0x25] = 0x0025,
[0x26] = 0x0026,
[0x27] = 0x0027,
[0x28] = 0x0028,
[0x29] = 0x0029,
[0x2A] = 0x002A,
[0x2B] = 0x002B,
[0x2C] = 0x002C,
[0x2D] = 0x002D,
[0x2E] = 0x002E,
[0x2F] = 0x002F,
[0x30] = 0x0030,
[0x31] = 0x0031,
[0x32] = 0x0032,
[0x33] = 0x0033,
[0x34] = 0x0034,
[0x35] = 0x0035,
[0x36] = 0x0036,
[0x37] = 0x0037,
[0x38] = 0x0038,
[0x39] = 0x0039,
[0x3A] = 0x003A,
[0x3B] = 0x003B,
[0x3C] = 0x003C,
[0x3D] = 0x003D,
[0x3E] = 0x003E,
[0x3F] = 0x003F,
[0x40] = 0x0040,
[0x41] = 0x0041,
[0x42] = 0x0042,
[0x43] = 0x0043,
[0x44] = 0x0044,
[0x45] = 0x0045,
[0x46] = 0x0046,
[0x47] = 0x0047,
[0x48] = 0x0048,
[0x49] = 0x0049,
[0x4A] = 0x004A,
[0x4B] = 0x004B,
[0x4C] = 0x004C,
[0x4D] = 0x004D,
[0x4E] = 0x004E,
[0x4F] = 0x004F,
[0x50] = 0x0050,
[0x51] = 0x0051,
[0x52] = 0x0052,
[0x53] = 0x0053,
[0x54] = 0x0054,
[0x55] = 0x0055,
[0x56] = 0x0056,
[0x57] = 0x0057,
[0x58] = 0x0058,
[0x59] = 0x0059,
[0x5A] = 0x005A,
[0x5B] = 0x005B,
[0x5C] = 0x005C,
[0x5D] = 0x005D,
[0x5E] = 0x005E,
[0x5F] = 0x005F,
[0x60] = 0x0060,
[0x61] = 0x0061,
[0x62] = 0x0062,
[0x63] = 0x0063,
[0x64] = 0x0064,
[0x65] = 0x0065,
[0x66] = 0x0066,
[0x67] = 0x0067,
[0x68] = 0x0068,
[0x69] = 0x0069,
[0x6A] = 0x006A,
[0x6B] = 0x006B,
[0x6C] = 0x006C,
[0x6D] = 0x006D,
[0x6E] = 0x006E,
[0x6F] = 0x006F,
[0x70] = 0x0070,
[0x71] = 0x0071,
[0x72] = 0x0072,
[0x73] = 0x0073,
[0x74] = 0x0074,
[0x75] = 0x0075,
[0x76] = 0x0076,
[0x77] = 0x0077,
[0x78] = 0x0078,
[0x79] = 0x0079,
[0x7A] = 0x007A,
[0x7B] = 0x007B,
[0x7C] = 0x007C,
[0x7D] = 0x007D,
[0x7E] = 0x007E,
[0x7F] = 0x007F,
[0x80] = 0x00C7,
[0x81] = 0x00FC,
[0x82] = 0x00E9,
[0x83] = 0x00E2,
[0x84] = 0x00E4,
[0x85] = 0x00E0,
[0x86] = 0x00E5,
[0x87] = 0x00E7,
[0x88] = 0x00EA,
[0x89] = 0x00EB,
[0x8A] = 0x00E8,
[0x8B] = 0x00EF,
[0x8C] = 0x00EE,
[0x8D] = 0x00EC,
[0x8E] = 0x00C4,
[0x8F] = 0x00C5,
[0x90] = 0x00C9,
[0x91] = 0x00E6,
[0x92] = 0x00C6,
[0x93] = 0x00F4,
[0x94] = 0x00F6,
[0x95] = 0x00F2,
[0x96] = 0x00FB,
[0x97] = 0x00F9,
[0x98] = 0x00FF,
[0x99] = 0x00D6,
[0x9A] = 0x00DC,
[0x9B] = 0x00A2,
[0x9C] = 0x00A3,
[0x9D] = 0x00A5,
[0x9E] = 0x20A7,
[0x9F] = 0x0192,
[0xA0] = 0x00E1,
[0xA1] = 0x00ED,
[0xA2] = 0x00F3,
[0xA3] = 0x00FA,
[0xA4] = 0x00F1,
[0xA5] = 0x00D1,
[0xA6] = 0x00AA,
[0xA7] = 0x00BA,
[0xA8] = 0x00BF,
[0xA9] = 0x2310,
[0xAA] = 0x00AC,
[0xAB] = 0x00BD,
[0xAC] = 0x00BC,
[0xAD] = 0x00A1,
[0xAE] = 0x00AB,
[0xAF] = 0x00BB,
[0xB0] = 0x2591,
[0xB1] = 0x2592,
[0xB2] = 0x2593,
[0xB3] = 0x2502,
[0xB4] = 0x2524,
[0xB5] = 0x2561,
[0xB6] = 0x2562,
[0xB7] = 0x2556,
[0xB8] = 0x2555,
[0xB9] = 0x2563,
[0xBA] = 0x2551,
[0xBB] = 0x2557,
[0xBC] = 0x255D,
[0xBD] = 0x255C,
[0xBE] = 0x255B,
[0xBF] = 0x2510,
[0xC0] = 0x2514,
[0xC1] = 0x2534,
[0xC2] = 0x252C,
[0xC3] = 0x251C,
[0xC4] = 0x2500,
[0xC5] = 0x253C,
[0xC6] = 0x255E,
[0xC7] = 0x255F,
[0xC8] = 0x255A,
[0xC9] = 0x2554,
[0xCA] = 0x2569,
[0xCB] = 0x2566,
[0xCC] = 0x2560,
[0xCD] = 0x2550,
[0xCE] = 0x256C,
[0xCF] = 0x2567,
[0xD0] = 0x2568,
[0xD1] = 0x2564,
[0xD2] = 0x2565,
[0xD3] = 0x2559,
[0xD4] = 0x2558,
[0xD5] = 0x2552,
[0xD6] = 0x2553,
[0xD7] = 0x256B,
[0xD8] = 0x256A,
[0xD9] = 0x2518,
[0xDA] = 0x250C,
[0xDB] = 0x2588,
[0xDC] = 0x2584,
[0xDD] = 0x258C,
[0xDE] = 0x2590,
[0xDF] = 0x2580,
[0xE0] = 0x03B1,
[0xE1] = 0x00DF,
[0xE2] = 0x0393,
[0xE3] = 0x03C0,
[0xE4] = 0x03A3,
[0xE5] = 0x03C3,
[0xE6] = 0x00B5,
[0xE7] = 0x03C4,
[0xE8] = 0x03A6,
[0xE9] = 0x0398,
[0xEA] = 0x03A9,
[0xEB] = 0x03B4,
[0xEC] = 0x221E,
[0xED] = 0x03C6,
[0xEE] = 0x03B5,
[0xEF] = 0x2229,
[0xF0] = 0x2261,
[0xF1] = 0x00B1,
[0xF2] = 0x2265,
[0xF3] = 0x2264,
[0xF4] = 0x2320,
[0xF5] = 0x2321,
[0xF6] = 0x00F7,
[0xF7] = 0x2248,
[0xF8] = 0x00B0,
[0xF9] = 0x2219,
[0xFA] = 0x00B7,
[0xFB] = 0x221A,
[0xFC] = 0x207F,
[0xFD] = 0x00B2,
[0xFE] = 0x25A0,
[0xFF] = 0x0020
}

+ 15
- 0
table_ocpalette.lua View File

@@ -0,0 +1,15 @@
oc_palette = {}

for i=0,255 do
if (i < 16) then
oc_palette[i] = (i * 15) << 16 | (i * 15) << 8 | (i * 15)
else
local j = i - 16
local b = math.floor((j % 5) * 255 / 4.0)
local g = math.floor((math.floor(j / 5.0) % 8) * 255 / 7.0)
local r = math.floor((math.floor(j / 40.0) % 6) * 255 / 5.0)
oc_palette[i] = r << 16 | g << 8 | b
end
end

return oc_palette

+ 549
- 0
video.lua View File

@@ -0,0 +1,549 @@
-- SUPPORTED MODES:
-- CGA: 00h, 01h, 02h, 03h, 04h, 05h, 06h
-- PCjr: 08h, 09h
-- VGA: 12h (pretend; just runs 13h code, used to not upset certain MCGA software)
-- MCGA: 13h (shaky; 0x3D** ports only supported for palette)

local video_mode = 3

pc_16_colors = {
0x000000, 0x0000AA, 0x00AA00, 0x00AAAA,
0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA,
0x555555, 0x5555FF, 0x55FF55, 0x55FFFF,
0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF
}

RAM[0x450] = 0 -- cursor pos
RAM[0x451] = 0
RAM[0x462] = 0 -- current page
RAM:w16(0x463, 0x3D4) -- base IO port for video

local vram = {}
local dirty_lines = {}

function video_pop_dirty_lines()
local d = dirty_lines
dirty_lines = {}
return d
end

function video_vram_read(addr)
return vram[addr - 0x9FFFF] or 0
end

function video_vram_write(addr, val)
if vram[addr - 0x9FFFF] == val then return end
vram[addr - 0x9FFFF] = val

local l = nil
local lx = nil

if (video_mode >= 0 and video_mode <= 3) or video_mode == 7 then
if addr >= 0xB8000 and addr < (0xB8000 + (25 * 160)) then
l = math.floor((addr - 0xB8000) / 160)
lx = math.floor((addr - 0xB8000) % 160) >> 1
end
elseif ((video_mode >= 4 and video_mode <= 6) and (addr >= 0xB8000 and addr < 0xBC000))
or (video_mode == 8 and (addr >= 0xB0000 and addr < 0xB4000)) then

if ((addr & 0x1FFF) < 8000) then
-- two banks, 100 lines each, so 80 bytes per line
l = (math.floor((addr & 0x1FFF) / 80)) >> 1 -- 0..99 div 2
lx = math.floor((addr & 0x1FFF) % 80)
end
elseif video_mode == 9 and (addr >= 0xB0000 and addr < 0xB8000) then

if ((addr & 0x1FFF) < 8000) then
-- four banks! 50 lines each, so 160 bytes per line
l = (math.floor((addr & 0x1FFF) / 160))
lx = math.floor((addr & 0x1FFF) % 160)
end
elseif video_mode >= 0x12 and video_mode <= 0x13 then
if (addr >= 0xA0000 and addr < (0xA0000 + 64000)) then
-- woo, no interleaving on this one
lx = math.floor(((addr - 0xA0000) >> 1) % 160) -- 0..159
l = math.floor((addr - 0xA0000) / 1280) -- 0..49
end
end

if l then
if not dirty_lines[l] then
dirty_lines[l] = {lx, lx}
else
local ld = dirty_lines[l]
if lx < ld[1] then ld[1] = lx
elseif lx > ld[2] then ld[2] = lx end
end
end
end

function video_text_addr(x, y)
return 0xB8000 + (y * 160) + (x * 2)
end

function video_scroll_up(lines, empty_attr, y1, x1, y2, x2)
if lines == 0 then
for y=y1,y2 do
for x=x1,x2 do
RAM:w16(video_text_addr(x,y), empty_attr << 8)
end
end
else
for y=y1+lines,y2 do
for x=x1,x2 do
RAM:w16(video_text_addr(x,y-lines), RAM:r16(video_text_addr(x,y)))
end
end
for y=y2-lines+1,y2 do
for x=x1,x2 do
RAM:w16(video_text_addr(x,y), empty_attr << 8)
end
end
end
end

function video_scroll_down(lines, empty_attr, y1, x1, y2, x2)
if lines == 0 then
for y=y1,y2 do
for x=x1,x2 do
RAM:w16(video_text_addr(x,y), empty_attr << 8)
end
end
else
for y=y2-lines,y1,-1 do
for x=x1,x2 do
RAM:w16(video_text_addr(x,y+lines), RAM:r16(video_text_addr(x,y)))
end
end
for y=y1,y1+lines-1 do
for x=x1,x2 do
RAM:w16(video_text_addr(x,y), empty_attr << 8)
end
end
end
end

local cga_mode = 0x08
local cga_palette = 0x30
local cga_status = 0x09

function cga_get_palette()
return cga_palette
end

--local herc_status = 0
local herc_status = 0xFF

function video_update()
-- herc_status = herc_status ~ 0x80
if video_mode == 0 or video_mode == 1 then
platform_render_text(vram, 0xB8000 - 0x9FFFF, 40, 25, 160)
elseif video_mode == 2 or video_mode == 3 or video_mode == 7 then
platform_render_text(vram, 0xB8000 - 0x9FFFF, 80, 25, 160)
elseif video_mode >= 4 and video_mode <= 6 then
if video_mode < 6 and platform_render_cga_color then
platform_render_cga_color(vram, 0xB8000 - 0x9FFFF, video_mode)
else
platform_render_cga_mono(vram, 0xB8000 - 0x9FFFF)
end
elseif video_mode == 0x08 then
platform_render_pcjr_160(vram, 0xB0000 - 0x9FFFF)
elseif video_mode == 0x09 then
platform_render_pcjr_320(vram, 0xB0000 - 0x9FFFF)
elseif video_mode == 0x0A then
platform_render_pcjr_640(vram, 0xB0000 - 0x9FFFF)
elseif video_mode == 0x13 or video_mode == 0x12 then
platform_render_mcga_13h(vram, 0xA0000 - 0x9FFFF)
end
end

local cga_mode_mask = 0x17 -- 0b10111
local cga_reg_to_mode = {}
cga_reg_to_mode[0] = 0x04 -- 40x25 mono
cga_reg_to_mode[1] = 0x00 -- 40x25 color
cga_reg_to_mode[2] = 0x05 -- 80x25 mono
cga_reg_to_mode[3] = 0x01 -- 80x25 color
cga_reg_to_mode[4] = 0x02 -- 320x200 graphics
cga_reg_to_mode[5] = 0x06 -- 320x200 alt graphics
cga_reg_to_mode[6] = 0x16 -- 640x200 graphics

-- Hercules ports
cpu_port_set(0x3BA, function(cond, val)
if not val then
emu_debug(2, "polling hercules status")
return herc_status
end
end)

-- CGA ports
cpu_port_set(0x3D8, function(cond, val)
if not val then return cga_mode else
cga_mode = val & 0x3F
for i=0,6 do
if cga_reg_to_mode[i] == (cga_mode & cga_mode_mask) then
-- TODO: set mode from here
end
end
RAM[0x465] = cga_mode
end
end)
cpu_port_set(0x3D9, function(cond, val)
if not val then return cga_palette else
cga_palette = val & 0x3F
RAM[0x466] = cga_palette
end
end)
cpu_port_set(0x3DA, function(cond, val)
if not val then
cga_status = cga_status ~ 0x09
return cga_status
end
end)
cpu_port_set(0x3DF, function(cond, val)
if not val then return 0 end
end)

function video_set_mode(vmode, clr)
local mode_supported = false
if (vmode >= 4 and vmode <= 6) and platform_render_cga_mono then
mode_supported = true
elseif vmode == 0x08 and platform_render_pcjr_160 then
mode_supported = true
elseif vmode == 0x09 and platform_render_pcjr_320 then
mode_supported = true
elseif vmode == 0x0A and platform_render_pcjr_640 then
mode_supported = true
elseif (vmode == 0x13 or vmode == 0x12) and platform_render_mcga_13h then
mode_supported = true
elseif vmode >= 0 and vmode <= 3 then
mode_supported = true
end
if not mode_supported then
return false
end

video_mode = vmode

cga_mode = 0x08
if vmode >= 0 and vmode <= 6 then
cga_mode = cga_mode | cga_reg_to_mode[vmode]
end
cga_status = 0x09

-- 0x449 - byte, video mode
RAM[0x449] = video_mode

-- 0x44A - word, text width
if video_mode == 7 or (cga_mode & 1) == 1 then
RAM[0x44A] = 80
else
RAM[0x44A] = 40
end
RAM[0x44B] = 0
RAM[0x465] = cga_mode
RAM[0x466] = cga_palette

if clr then
if (vmode >= 0 and vmode <= 3) or vmode == 7 then
for y=0,24 do
for x=0,RAM[0x44A]-1 do
RAM:w16(video_text_addr(x,y), 0x0700)
end
end
else
for i=0,7999 do
RAM[0xB8000 + i] = 0
RAM[0xBA000 + i] = 0
end
end
end

return true
end

video_set_mode(3, true)

function video_get_cursor(page)
if page == nil then
page = (RAM[0x462] & 7) << 1
end
return RAM[0x450+page], RAM[0x451+page]
end

function video_set_cursor(x, y, page)
if page == nil then
page = (RAM[0x462] & 7) << 1
end
RAM[0x450+page] = x
RAM[0x451+page] = y
end

local vgapal={}

function video_vga_get_palette(i)
return vgapal[1+i] or 0xFF00FF
end

function video_vga_get_palette_orig(i)
-- just wing it this time, should be good enough
return (video_vga_get_palette(i) >> 2) & 0x3F3F3F
end

function video_vga_set_palette(i,r,g,b)
local rs = math.floor(r * 255 / 63.0) & 0xFF
local gs = math.floor(g * 255 / 63.0) & 0xFF
local bs = math.floor(b * 255 / 63.0) & 0xFF
vgapal[1+i] = (rs<<16)|(gs<<8)|bs

if video_mode >= 0x10 then
for i=0,49 do dirty_lines[i] = {0,159} end
end
end

for i=1,16 do
vgapal[i] = pc_16_colors[i]
end

-- VGA DAC
local dac_pal_idx = 0
local dac_write = true

cpu_port_set(0x3C7, function(cond, val)
if not val then return 0 else
dac_pal_idx = (val & 0xFF) * 3
dac_write = false
end
end)
cpu_port_set(0x3C8, function(cond, val)
if not val then return 0 else
dac_pal_idx = (val & 0xFF) * 3
dac_write = true
end
end)
cpu_port_set(0x3C9, function(cond, val)
local shift = (8*(2 - math.floor(dac_pal_idx % 3)))
local idx = math.floor(dac_pal_idx / 3)
local mask = 0xFF << shift
if not val and not dac_write then
dac_pal_idx = math.floor((dac_pal_idx + 1) % 768)
return (video_vga_get_palette_orig(idx) >> (shift)) & 0xFF
elseif val and dac_write then
dac_pal_idx = math.floor((dac_pal_idx + 1) % 768)
local pal = video_vga_get_palette_orig(idx) & (mask ~ 0xFFFFFF)
pal = pal | ((val & 0xFF) << shift)
video_vga_set_palette(idx, (pal >> 16) & 0xFF, (pal >> 8) & 0xFF, pal & 0xFF)
elseif not val then return 0 end
end)

-- interrupt
cpu_register_interrupt_handler(0x10, function(ax,ah,al)
if ah == 0x00 then
-- set video mode
local mode = al & 0xFF7F
emu_debug(1, "setting mode to " .. mode)
video_set_mode(mode, (al & 0x80) == 0)
return true
elseif ah == 0x01 then
-- set cursor shape, TODO
return true
elseif ah == 0x02 then
-- set cursor pos
video_set_cursor(CPU["regs"][3] & 0xFF, CPU["regs"][3] >> 8, CPU["regs"][4] >> 8)
return true
elseif ah == 0x03 then
-- get cursor_pos
local cursor_x, cursor_y = video_get_cursor(CPU["regs"][4] >> 8)
CPU["regs"][3] = (cursor_y << 8) | (cursor_x)
return true
elseif ah == 0x04 then
-- query light pen
CPU["regs"][1] = al -- AH=0, not triggered
return true
elseif ah == 0x05 then
-- select video page
RAM[0x462] = al & 0x07
return true
elseif ah == 0x06 then
-- scroll up
video_scroll_up(al, (CPU["regs"][4] >> 8),
(CPU["regs"][2] >> 8), (CPU["regs"][2] & 0xFF),