Lua 5.3 didn't need an x86 emulator. But now, it has one regardless.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

video.lua 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. -- SUPPORTED MODES:
  2. -- CGA: 00h, 01h, 02h, 03h, 04h, 05h, 06h
  3. -- PCjr: 08h, 09h
  4. -- VGA: 12h (pretend; just runs 13h code, used to not upset certain MCGA software)
  5. -- MCGA: 13h (shaky; 0x3D** ports only supported for palette)
  6. local video_mode = 3
  7. pc_16_colors = {
  8. 0x000000, 0x0000AA, 0x00AA00, 0x00AAAA,
  9. 0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA,
  10. 0x555555, 0x5555FF, 0x55FF55, 0x55FFFF,
  11. 0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF
  12. }
  13. RAM[0x450] = 0 -- cursor pos
  14. RAM[0x451] = 0
  15. RAM[0x462] = 0 -- current page
  16. RAM:w16(0x463, 0x3D4) -- base IO port for video
  17. local vram = {}
  18. local dirty_lines = {}
  19. function video_pop_dirty_lines()
  20. local d = dirty_lines
  21. dirty_lines = {}
  22. return d
  23. end
  24. function video_vram_read(addr)
  25. return vram[addr - 0x9FFFF] or 0
  26. end
  27. function video_vram_write(addr, val)
  28. if vram[addr - 0x9FFFF] == val then return end
  29. vram[addr - 0x9FFFF] = val
  30. local l = nil
  31. local lx = nil
  32. if (video_mode >= 0 and video_mode <= 3) or video_mode == 7 then
  33. if addr >= 0xB8000 and addr < (0xB8000 + (25 * 160)) then
  34. l = math.floor((addr - 0xB8000) / 160)
  35. lx = math.floor((addr - 0xB8000) % 160) >> 1
  36. end
  37. elseif ((video_mode >= 4 and video_mode <= 6) and (addr >= 0xB8000 and addr < 0xBC000))
  38. or (video_mode == 8 and (addr >= 0xB0000 and addr < 0xB4000)) then
  39. if ((addr & 0x1FFF) < 8000) then
  40. -- two banks, 100 lines each, so 80 bytes per line
  41. l = (math.floor((addr & 0x1FFF) / 80)) >> 1 -- 0..99 div 2
  42. lx = math.floor((addr & 0x1FFF) % 80)
  43. end
  44. elseif video_mode == 9 and (addr >= 0xB0000 and addr < 0xB8000) then
  45. if ((addr & 0x1FFF) < 8000) then
  46. -- four banks! 50 lines each, so 160 bytes per line
  47. l = (math.floor((addr & 0x1FFF) / 160))
  48. lx = math.floor((addr & 0x1FFF) % 160)
  49. end
  50. elseif video_mode >= 0x12 and video_mode <= 0x13 then
  51. if (addr >= 0xA0000 and addr < (0xA0000 + 64000)) then
  52. -- woo, no interleaving on this one
  53. lx = math.floor(((addr - 0xA0000) >> 1) % 160) -- 0..159
  54. l = math.floor((addr - 0xA0000) / 1280) -- 0..49
  55. end
  56. end
  57. if l then
  58. if not dirty_lines[l] then
  59. dirty_lines[l] = {lx, lx}
  60. else
  61. local ld = dirty_lines[l]
  62. if lx < ld[1] then ld[1] = lx
  63. elseif lx > ld[2] then ld[2] = lx end
  64. end
  65. end
  66. end
  67. function video_text_addr(x, y)
  68. return 0xB8000 + (y * 160) + (x * 2)
  69. end
  70. function video_scroll_up(lines, empty_attr, y1, x1, y2, x2)
  71. if lines == 0 then
  72. for y=y1,y2 do
  73. for x=x1,x2 do
  74. RAM:w16(video_text_addr(x,y), empty_attr << 8)
  75. end
  76. end
  77. else
  78. for y=y1+lines,y2 do
  79. for x=x1,x2 do
  80. RAM:w16(video_text_addr(x,y-lines), RAM:r16(video_text_addr(x,y)))
  81. end
  82. end
  83. for y=y2-lines+1,y2 do
  84. for x=x1,x2 do
  85. RAM:w16(video_text_addr(x,y), empty_attr << 8)
  86. end
  87. end
  88. end
  89. end
  90. function video_scroll_down(lines, empty_attr, y1, x1, y2, x2)
  91. if lines == 0 then
  92. for y=y1,y2 do
  93. for x=x1,x2 do
  94. RAM:w16(video_text_addr(x,y), empty_attr << 8)
  95. end
  96. end
  97. else
  98. for y=y2-lines,y1,-1 do
  99. for x=x1,x2 do
  100. RAM:w16(video_text_addr(x,y+lines), RAM:r16(video_text_addr(x,y)))
  101. end
  102. end
  103. for y=y1,y1+lines-1 do
  104. for x=x1,x2 do
  105. RAM:w16(video_text_addr(x,y), empty_attr << 8)
  106. end
  107. end
  108. end
  109. end
  110. local cga_mode = 0x08
  111. local cga_palette = 0x30
  112. local cga_status = 0x09
  113. function cga_get_palette()
  114. return cga_palette
  115. end
  116. --local herc_status = 0
  117. local herc_status = 0xFF
  118. function video_update()
  119. -- herc_status = herc_status ~ 0x80
  120. if video_mode == 0 or video_mode == 1 then
  121. platform_render_text(vram, 0xB8000 - 0x9FFFF, 40, 25, 160)
  122. elseif video_mode == 2 or video_mode == 3 or video_mode == 7 then
  123. platform_render_text(vram, 0xB8000 - 0x9FFFF, 80, 25, 160)
  124. elseif video_mode >= 4 and video_mode <= 6 then
  125. if video_mode < 6 and platform_render_cga_color then
  126. platform_render_cga_color(vram, 0xB8000 - 0x9FFFF, video_mode)
  127. else
  128. platform_render_cga_mono(vram, 0xB8000 - 0x9FFFF)
  129. end
  130. elseif video_mode == 0x08 then
  131. platform_render_pcjr_160(vram, 0xB0000 - 0x9FFFF)
  132. elseif video_mode == 0x09 then
  133. platform_render_pcjr_320(vram, 0xB0000 - 0x9FFFF)
  134. elseif video_mode == 0x0A then
  135. platform_render_pcjr_640(vram, 0xB0000 - 0x9FFFF)
  136. elseif video_mode == 0x13 or video_mode == 0x12 then
  137. platform_render_mcga_13h(vram, 0xA0000 - 0x9FFFF)
  138. end
  139. end
  140. local cga_mode_mask = 0x17 -- 0b10111
  141. local cga_reg_to_mode = {}
  142. cga_reg_to_mode[0] = 0x04 -- 40x25 mono
  143. cga_reg_to_mode[1] = 0x00 -- 40x25 color
  144. cga_reg_to_mode[2] = 0x05 -- 80x25 mono
  145. cga_reg_to_mode[3] = 0x01 -- 80x25 color
  146. cga_reg_to_mode[4] = 0x02 -- 320x200 graphics
  147. cga_reg_to_mode[5] = 0x06 -- 320x200 alt graphics
  148. cga_reg_to_mode[6] = 0x16 -- 640x200 graphics
  149. -- Hercules ports
  150. cpu_port_set(0x3BA, function(cond, val)
  151. if not val then
  152. emu_debug(2, "polling hercules status")
  153. return herc_status
  154. end
  155. end)
  156. -- CGA ports
  157. cpu_port_set(0x3D8, function(cond, val)
  158. if not val then return cga_mode else
  159. cga_mode = val & 0x3F
  160. for i=0,6 do
  161. if cga_reg_to_mode[i] == (cga_mode & cga_mode_mask) then
  162. -- TODO: set mode from here
  163. end
  164. end
  165. RAM[0x465] = cga_mode
  166. end
  167. end)
  168. cpu_port_set(0x3D9, function(cond, val)
  169. if not val then return cga_palette else
  170. cga_palette = val & 0x3F
  171. RAM[0x466] = cga_palette
  172. end
  173. end)
  174. cpu_port_set(0x3DA, function(cond, val)
  175. if not val then
  176. cga_status = cga_status ~ 0x09
  177. return cga_status
  178. end
  179. end)
  180. cpu_port_set(0x3DF, function(cond, val)
  181. if not val then return 0 end
  182. end)
  183. function video_set_mode(vmode, clr)
  184. local mode_supported = false
  185. if (vmode >= 4 and vmode <= 6) and platform_render_cga_mono then
  186. mode_supported = true
  187. elseif vmode == 0x08 and platform_render_pcjr_160 then
  188. mode_supported = true
  189. elseif vmode == 0x09 and platform_render_pcjr_320 then
  190. mode_supported = true
  191. elseif vmode == 0x0A and platform_render_pcjr_640 then
  192. mode_supported = true
  193. elseif (vmode == 0x13 or vmode == 0x12) and platform_render_mcga_13h then
  194. mode_supported = true
  195. elseif vmode >= 0 and vmode <= 3 then
  196. mode_supported = true
  197. end
  198. if not mode_supported then
  199. return false
  200. end
  201. video_mode = vmode
  202. cga_mode = 0x08
  203. if vmode >= 0 and vmode <= 6 then
  204. cga_mode = cga_mode | cga_reg_to_mode[vmode]
  205. end
  206. cga_status = 0x09
  207. -- 0x449 - byte, video mode
  208. RAM[0x449] = video_mode
  209. -- 0x44A - word, text width
  210. if video_mode == 7 or (cga_mode & 1) == 1 then
  211. RAM[0x44A] = 80
  212. else
  213. RAM[0x44A] = 40
  214. end
  215. RAM[0x44B] = 0
  216. RAM[0x465] = cga_mode
  217. RAM[0x466] = cga_palette
  218. if clr then
  219. if (vmode >= 0 and vmode <= 3) or vmode == 7 then
  220. for y=0,24 do
  221. for x=0,RAM[0x44A]-1 do
  222. RAM:w16(video_text_addr(x,y), 0x0700)
  223. end
  224. end
  225. else
  226. for i=0,7999 do
  227. RAM[0xB8000 + i] = 0
  228. RAM[0xBA000 + i] = 0
  229. end
  230. end
  231. end
  232. return true
  233. end
  234. video_set_mode(3, true)
  235. function video_get_cursor(page)
  236. if page == nil then
  237. page = (RAM[0x462] & 7) << 1
  238. end
  239. return RAM[0x450+page], RAM[0x451+page]
  240. end
  241. function video_set_cursor(x, y, page)
  242. if page == nil then
  243. page = (RAM[0x462] & 7) << 1
  244. end
  245. RAM[0x450+page] = x
  246. RAM[0x451+page] = y
  247. end
  248. local vgapal={}
  249. function video_vga_get_palette(i)
  250. return vgapal[1+i] or 0xFF00FF
  251. end
  252. function video_vga_get_palette_orig(i)
  253. -- just wing it this time, should be good enough
  254. return (video_vga_get_palette(i) >> 2) & 0x3F3F3F
  255. end
  256. function video_vga_set_palette(i,r,g,b)
  257. local rs = math.floor(r * 255 / 63.0) & 0xFF
  258. local gs = math.floor(g * 255 / 63.0) & 0xFF
  259. local bs = math.floor(b * 255 / 63.0) & 0xFF
  260. vgapal[1+i] = (rs<<16)|(gs<<8)|bs
  261. if video_mode >= 0x10 then
  262. for i=0,49 do dirty_lines[i] = {0,159} end
  263. end
  264. end
  265. for i=1,16 do
  266. vgapal[i] = pc_16_colors[i]
  267. end
  268. -- VGA DAC
  269. local dac_pal_idx = 0
  270. local dac_write = true
  271. cpu_port_set(0x3C7, function(cond, val)
  272. if not val then return 0 else
  273. dac_pal_idx = (val & 0xFF) * 3
  274. dac_write = false
  275. end
  276. end)
  277. cpu_port_set(0x3C8, function(cond, val)
  278. if not val then return 0 else
  279. dac_pal_idx = (val & 0xFF) * 3
  280. dac_write = true
  281. end
  282. end)
  283. cpu_port_set(0x3C9, function(cond, val)
  284. local shift = (8*(2 - math.floor(dac_pal_idx % 3)))
  285. local idx = math.floor(dac_pal_idx / 3)
  286. local mask = 0xFF << shift
  287. if not val and not dac_write then
  288. dac_pal_idx = math.floor((dac_pal_idx + 1) % 768)
  289. return (video_vga_get_palette_orig(idx) >> (shift)) & 0xFF
  290. elseif val and dac_write then
  291. dac_pal_idx = math.floor((dac_pal_idx + 1) % 768)
  292. local pal = video_vga_get_palette_orig(idx) & (mask ~ 0xFFFFFF)
  293. pal = pal | ((val & 0xFF) << shift)
  294. video_vga_set_palette(idx, (pal >> 16) & 0xFF, (pal >> 8) & 0xFF, pal & 0xFF)
  295. elseif not val then return 0 end
  296. end)
  297. -- interrupt
  298. cpu_register_interrupt_handler(0x10, function(ax,ah,al)
  299. if ah == 0x00 then
  300. -- set video mode
  301. local mode = al & 0xFF7F
  302. emu_debug(1, "setting mode to " .. mode)
  303. video_set_mode(mode, (al & 0x80) == 0)
  304. return true
  305. elseif ah == 0x01 then
  306. -- set cursor shape, TODO
  307. return true
  308. elseif ah == 0x02 then
  309. -- set cursor pos
  310. video_set_cursor(CPU["regs"][3] & 0xFF, CPU["regs"][3] >> 8, CPU["regs"][4] >> 8)
  311. return true
  312. elseif ah == 0x03 then
  313. -- get cursor_pos
  314. local cursor_x, cursor_y = video_get_cursor(CPU["regs"][4] >> 8)
  315. CPU["regs"][3] = (cursor_y << 8) | (cursor_x)
  316. return true
  317. elseif ah == 0x04 then
  318. -- query light pen
  319. CPU["regs"][1] = al -- AH=0, not triggered
  320. return true
  321. elseif ah == 0x05 then
  322. -- select video page
  323. RAM[0x462] = al & 0x07
  324. return true
  325. elseif ah == 0x06 then
  326. -- scroll up
  327. video_scroll_up(al, (CPU["regs"][4] >> 8),
  328. (CPU["regs"][2] >> 8), (CPU["regs"][2] & 0xFF),
  329. (CPU["regs"][3] >> 8), (CPU["regs"][3] & 0xFF));
  330. return true
  331. elseif ah == 0x07 then
  332. -- scroll down
  333. video_scroll_down(al, (CPU["regs"][4] >> 8),
  334. (CPU["regs"][2] >> 8), (CPU["regs"][2] & 0xFF),
  335. (CPU["regs"][3] >> 8), (CPU["regs"][3] & 0xFF));
  336. return true
  337. elseif ah == 0x08 then
  338. -- read character
  339. local cursor_x, cursor_y = video_get_cursor(CPU["regs"][4] >> 8)
  340. local addr = video_text_addr(cursor_x, cursor_y)
  341. CPU["regs"][1] = 0x0800 | RAM[addr]
  342. CPU["regs"][4] = (CPU["regs"][4] & 0xFF) | (RAM[addr + 1] << 8)
  343. return true
  344. elseif ah == 0x09 or ah == 0x0A then
  345. -- write character/attribute (0x09) or char (0x0A)
  346. local cursor_x, cursor_y = video_get_cursor(CPU["regs"][4] >> 8)
  347. local addr = video_text_addr(cursor_x, cursor_y)
  348. local bl = CPU["regs"][4] & 0xFF
  349. for i=1,CPU["regs"][2] do
  350. if addr < (160*25) then
  351. RAM[addr] = al
  352. if ah == 0x09 then
  353. RAM[addr + 1] = bl
  354. end
  355. addr = addr + 2
  356. end
  357. end
  358. return true
  359. elseif ah == 0x0B then
  360. -- configure video
  361. local p = cga_palette
  362. local bh = CPU["regs"][4] >> 8
  363. local bl = CPU["regs"][4] & 0xFF
  364. if bh == 0x00 then
  365. -- TODO: set border color
  366. elseif bh == 0x01 then
  367. -- set palette
  368. p = (p & 0xDF) | ((bl & 0x01) << 5)
  369. end
  370. cga_palette = p
  371. RAM[0x466] = p
  372. return true
  373. elseif ah == 0x0E then
  374. -- put char like a tty
  375. local cursor_x, cursor_y = video_get_cursor()
  376. local addr = video_text_addr(cursor_x, cursor_y)
  377. local cursor_width = RAM[0x44A]
  378. local cursor_height = 25
  379. if al == 0x0D then
  380. cursor_x = 0
  381. elseif al == 0x0A then
  382. if cursor_y < (cursor_height - 1) then
  383. cursor_y = cursor_y + 1
  384. else
  385. video_scroll_up(1, 0x07, 0, 0, cursor_height - 1, cursor_width - 1)
  386. end
  387. elseif al == 0x08 then
  388. if cursor_x > 0 then
  389. cursor_x = cursor_x - 1
  390. end
  391. RAM[video_text_addr(cursor_x, cursor_y)] = 0
  392. elseif al == 0x07 then
  393. -- bell
  394. else
  395. RAM[addr] = al
  396. cursor_x = cursor_x + 1
  397. if cursor_x >= cursor_width then
  398. cursor_x = 0
  399. if cursor_y < (cursor_height - 1) then
  400. cursor_y = cursor_y + 1
  401. else
  402. video_scroll_up(1, 0x07, 0, 0, cursor_height - 1, cursor_width - 1)
  403. end
  404. end
  405. end
  406. video_set_cursor(cursor_x, cursor_y)
  407. return true
  408. elseif ah == 0x0F then
  409. -- read video mode
  410. local ah = RAM[0x44A]
  411. local al = video_mode
  412. local bh = RAM[0x462]
  413. CPU["regs"][1] = (ah << 8) | (al)
  414. CPU["regs"][4] = (CPU["regs"][4] & 0xFF) | (bh << 8)
  415. return true
  416. elseif ax == 0x1010 then
  417. -- set single palette register
  418. -- BX=i, CH=g, CL=b, DH=r
  419. video_vga_set_palette(CPU["regs"][4] & 0xFF, CPU["regs"][3]>>8, CPU["regs"][2]>>8, CPU["regs"][2]&0xFF)
  420. return true
  421. elseif ax == 0x1012 then
  422. -- set palette block, BX = start, CX = count, ES:DX = offset
  423. local addrIn = seg(SEG_ES,CPU["regs"][3])
  424. local addrOut = CPU["regs"][4] - 1
  425. for i=1,CPU["regs"][2] do
  426. video_vga_set_palette((addrOut + i) & 0xFF, RAM[addrIn], RAM[addrIn + 1], RAM[addrIn + 2])
  427. addrIn = addrIn + 3
  428. end
  429. return true
  430. elseif ax == 0x1015 then
  431. -- read single palette register
  432. local col = video_vga_get_palette_orig(CPU["regs"][4] & 0xFF)
  433. CPU["regs"][2] = col & 0xFFFF
  434. CPU["regs"][3] = ((col >> 8) & 0xFF00) | (CPU["regs"][3] & 0xFF)
  435. return true
  436. elseif ax == 0x1017 then
  437. -- read palette block
  438. local addrIn = seg(SEG_ES,CPU["regs"][3])
  439. local addrOut = CPU["regs"][4] - 1
  440. for i=1,CPU["regs"][2] do
  441. local col = video_vga_get_palette_orig((addrOut + i) & 0xFF)
  442. RAM[addrIn] = (col >> 16) & 0xFF
  443. RAM[addrIn + 1] = (col >> 8) & 0xFF
  444. RAM[addrIn + 2] = col & 0xFF
  445. addrIn = addrIn + 3
  446. end
  447. return true
  448. elseif ax == 0x1130 then
  449. -- get font information
  450. local bh = CPU["regs"][4] >> 8
  451. if bh == 0 then
  452. -- set ES:BP
  453. CPU["regs"][6] = RAM:r16(0x1F * 4)
  454. CPU["segments"][1] = RAM:r16(0x1F * 4 + 2)
  455. elseif bh == 1 then
  456. CPU["regs"][6] = RAM:r16(0x43 * 4)
  457. CPU["segments"][1] = RAM:r16(0x43 * 4 + 2)
  458. else
  459. CPU["regs"][6] = 0x0800
  460. CPU["segments"][1] = 0xF000
  461. end
  462. CPU["regs"][2] = 0x08
  463. CPU["regs"][3] = (CPU["regs"][3] & 0xFF00) | RAM[0x484]
  464. return true
  465. elseif ax == 0x1A00 then
  466. -- get display combination code
  467. CPU["regs"][1] = 0x1A1A
  468. CPU["regs"][4] = 0x020A
  469. return true
  470. else
  471. cpu_set_flag(0)
  472. return false
  473. end
  474. end)
  475. -- CRTC ports
  476. local crtc_index = 0
  477. cpu_port_set(0x3D4, function(cond, val)
  478. if not val then return crtc_index
  479. elseif val >= 0 and val <= 0x11 then crtc_index = val end
  480. end)
  481. local cga_cursor = 0
  482. cpu_port_set(0x3D5, function(cond, val)
  483. if val then -- writes
  484. if crtc_index == 0x0E then
  485. cga_cursor = (cga_cursor & 0xFF) | (val & 0xFF)
  486. elseif crtc_index == 0x0F then
  487. cga_cursor = (cga_cursor & 0xFF00) | (val & 0xFF)
  488. end
  489. else -- reads
  490. if crtc_index == 0x0E then
  491. return (cga_cursor >> 8)
  492. elseif crtc_index == 0x0F then
  493. return (cga_cursor & 0xFF)
  494. else
  495. return 0
  496. end
  497. end
  498. end)
  499. -- mirroring
  500. for i=0,6,2 do
  501. cpu_port_set(0x3B0 + i, cpu_port_get(0x3D4))
  502. cpu_port_set(0x3D0 + i, cpu_port_get(0x3D4))
  503. cpu_port_set(0x3B1 + i, cpu_port_get(0x3D5))
  504. cpu_port_set(0x3D1 + i, cpu_port_get(0x3D5))
  505. end