_cashandler.tcl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. namespace eval cashandler {
  2. set help_text_cas \
  3. {-----------------------------------------------------------
  4. CAS-file tools 1.0 for openMSX Made By: NYYRIKKI & wouter_
  5. ------------------------------------------------------------
  6. Usage:
  7. casload <file> | Open CAS-file for load
  8. cassave <file> | Open CAS-file for save
  9. caslist | Show loaded CAS-file content
  10. casrun [<file>] [<number>] | Automatically run program
  11. caspos <number> | Select file to load from CAS
  12. caseject | Deactivate CAS-file support
  13. }
  14. set_help_text casload $help_text_cas
  15. set_help_text cassave $help_text_cas
  16. set_help_text caslist $help_text_cas
  17. set_help_text casrun $help_text_cas
  18. set_help_text caspos $help_text_cas
  19. set_help_text caseject $help_text_cas
  20. set_tabcompletion_proc casload utils::file_completion
  21. set_tabcompletion_proc cassave utils::file_completion
  22. set_tabcompletion_proc casrun utils::file_completion
  23. variable fidr "" ;# file id of opened read file, "" if not active
  24. variable fidw "" ;# file id of opened write file, "" if not active
  25. variable bptapion ;# tapion
  26. variable bptapin ;# tapin
  27. variable bptapoon ;# tapoon
  28. variable bptapout ;# tapout
  29. variable bptapoof ;# tapoof
  30. variable bphread ;# h.read
  31. variable casheader [binary format H* "1FA6DEBACC137D74"]
  32. variable casheaderSVI [binary format H* "555555555555555555555555555555557F"]
  33. variable casbios [dict create r [list 0x00E2 tapion 0x00E5 tapin 0x00E8 tapiof] \
  34. w [list 0x00EB tapoon 0x00EE tapout 0x00F1 tapoof]]
  35. variable casbiosSVI [dict create r [list 0x006A tapion 0x006D tapin 0x0070 tapiof] \
  36. w [list 0x0073 tapoon 0x0076 tapout 0x0079 tapoof]]
  37. proc casload {filename} {
  38. casopen $filename "r"
  39. return "Cassette inserted, overriding normal openMSX cassette load routines."
  40. }
  41. proc cassave {filename} {
  42. casopen $filename "w"
  43. return "Cassette inserted, overriding normal openMSX cassette save routines."
  44. }
  45. proc caseject {} {
  46. casclose "r"
  47. casclose "w"
  48. return "Cassette ejected, normal openMSX cassette routines in use."
  49. }
  50. proc casopen {filename rw} {
  51. # Possibly close previous file.
  52. casclose $rw
  53. # Open file.
  54. variable fid${rw}
  55. set fid${rw} [open $filename $rw]
  56. fconfigure [set fid${rw}] -translation binary -encoding binary
  57. # Install BIOS handlers.
  58. variable casbios
  59. variable casbiosSVI
  60. if {[machine_info type] eq "SVI"} {
  61. set bios $casbiosSVI
  62. } else {
  63. set bios $casbios
  64. }
  65. foreach {addr func} [dict get $bios $rw] {
  66. variable bp${func}
  67. if {[machine_info type] eq "SVI"} {
  68. set slotspec "3 X X"
  69. } else {
  70. set slotspec "0 0"
  71. }
  72. set bp${func} [debug set_bp [peek16 $addr "slotted memory"] "\[pc_in_slot {*}${slotspec}\]" [namespace code $func]]
  73. }
  74. }
  75. proc casclose {rw} {
  76. # Was active?
  77. variable fid${rw}
  78. if {[set fid${rw}] eq ""} return
  79. # Uninstall BIOS handlers.
  80. variable casbios
  81. variable casbiosSVI
  82. if {[machine_info type] eq "SVI"} {
  83. set bios $casbiosSVI
  84. } else {
  85. set bios $casbios
  86. }
  87. foreach {addr func} [dict get $bios $rw] {
  88. variable bp${func}
  89. debug remove_bp [set bp${func}]
  90. }
  91. if {$rw eq "r"} {
  92. # In case of read (possibly) also remove bphread.
  93. variable bphread
  94. catch {debug remove_bp $bphread} ;# often not set, so catch error
  95. } else {
  96. # In case of write align end of file in order to make combine with other CAS-files easy.
  97. set align [expr {[tell $fidw] & 7}]
  98. if {$align} {puts -nonewline $fidw [string repeat \x0 [expr {8 - $align}]]}
  99. }
  100. # Close file and deactivate.
  101. close [set fid${rw}]
  102. set fid${rw} ""
  103. }
  104. proc tapion {} {
  105. # TAPION
  106. # Address : #00E1
  107. # Function : Reads the header block after turning the cassette motor on
  108. # Output : C-flag set if failed
  109. # Registers: All
  110. if {[machine_info type] eq "SVI"} {
  111. seekheader
  112. } else {
  113. reg F [expr {([seekheader] == -1) ? 0x01 : 0x40}] ;# set carry flag if header not found
  114. }
  115. ret
  116. }
  117. proc tapin {} {
  118. # TAPIN
  119. # Address : #00E4
  120. # Function : Read data from the tape
  121. # Output : A - read value
  122. # C-flag set if failed
  123. # Registers: All
  124. variable fidr
  125. if {[binary scan [read $fidr 1] cu val]} {
  126. reg A $val
  127. reg F 0x40 ;# ok, clear carry flag
  128. } else {
  129. reg F 1 ;# end-of-file reached, set carry flag
  130. }
  131. ret
  132. }
  133. proc tapiof {} {
  134. #TAPIOF
  135. #Address : #00E7
  136. #Function : Stops reading from tape
  137. #Registers: None
  138. ret
  139. }
  140. proc tapoon {} {
  141. #TAPOON
  142. #Address : #00EA
  143. #Function : Turns on the cassette motor and writes the header
  144. #Input : A - #00 short header
  145. # not #00 long header
  146. #Output : C-flag set if failed
  147. #Registers: All
  148. if {[catch {
  149. variable fidw
  150. variable casheader
  151. variable casheaderSVI
  152. if {[machine_info type] eq "SVI"} {
  153. set header $casheaderSVI
  154. } else {
  155. set align [expr {[tell $fidw] & 7}]
  156. if {$align} {puts -nonewline $fidw [string repeat \x0 [expr {8 - $align}]]}
  157. set header $casheader
  158. }
  159. puts -nonewline $fidw $header
  160. if {[machine_info type] ne "SVI"} {
  161. reg F 0x40 ;# ok, clear carry flag
  162. }
  163. }]} {
  164. if {[machine_info type] ne "SVI"} {
  165. reg F 1 ;# write error, set carry flag
  166. }
  167. }
  168. ret
  169. }
  170. proc tapout {} {
  171. #TAPOUT
  172. #Address : #00ED
  173. #Function : Writes data on the tape
  174. #Input : A - data to write
  175. #Output : C-flag set if failed
  176. #Registers: All
  177. if {[catch {
  178. variable fidw
  179. puts -nonewline $fidw [binary format c* [reg A]]
  180. reg F 0x40 ;# all went fine, clear carry flag
  181. }]} {
  182. reg F 1 ;# write error, set carry flag
  183. }
  184. ret
  185. }
  186. proc tapoof {} {
  187. #TAPOOF
  188. #Address : #00F0
  189. #Function : Stops writing on the tape
  190. #Registers: None
  191. ret
  192. }
  193. proc ret {} {
  194. reg PC [peek16 [reg SP]]
  195. reg SP [expr {[reg SP] + 2}]
  196. }
  197. proc seekheader {} {
  198. variable fidr
  199. if {[machine_info type] ne "SVI"} {
  200. # Skip till 8-bytes aligned.
  201. set align [expr {[tell $fidr] & 7}]
  202. if {$align} {read $fidr [expr {8 - $align}]}
  203. }
  204. # Search header.
  205. if {[machine_info type] eq "SVI"} {
  206. while {true} {
  207. variable casheaderSVI
  208. set pos [tell $fidr]
  209. if {[eof $fidr]} {break}
  210. if {[read $fidr 17] eq $casheaderSVI} {break}
  211. seek $fidr $pos start
  212. read $fidr 1
  213. }
  214. # Return position of header in cas file, or -1 if not found.
  215. expr {[eof $fidr] ? -1 : $pos}
  216. } else {
  217. variable casheader
  218. while {![eof $fidr] && [read $fidr 8] ne $casheader} {}
  219. # Return position of header in cas file, or -1 if not found.
  220. expr {[eof $fidr] ? -1 : ([tell $fidr] - 8)}
  221. }
  222. }
  223. proc readheader {} {
  224. # Read (first) type-id byte.
  225. variable fidr
  226. set byte [read $fidr 1]
  227. if {![binary scan $byte cu val]} {return -1}
  228. # This must be one of 0xEA 0xD0 0xD3.
  229. set type [lsearch -exact -integer [list 0xEA 0xD0 0xD3] $val]
  230. if {$type == -1} {return -1}
  231. # And it must repeat 9 more times.
  232. for {set i 0} {$i < 9} {incr i} {
  233. if {[read $fidr 1] ne $byte} {return -1}
  234. }
  235. return $type
  236. }
  237. proc checkactive {} {
  238. variable fidr
  239. if {$fidr eq ""} {error "No cas file loaded, use 'casload <filename>'."}
  240. }
  241. proc caslist {} {
  242. checkactive
  243. set result "Position: Type: Name: Offset:\n"
  244. append result "--------------------------------\n"
  245. variable fidr
  246. set oldpos [tell $fidr]
  247. seek $fidr 0
  248. set i 0
  249. while {![eof $fidr]} {
  250. set headerpos [seekheader]
  251. set type [readheader]
  252. if {$type == -1} continue
  253. append result [expr {($headerpos < $oldpos) ? "| " : " "}]
  254. append result [format %5d [incr i]] " : "
  255. append result [lindex "TXT BIN BAS" $type] " : "
  256. append result [read $fidr 6] " : "
  257. append result $headerpos "\n"
  258. }
  259. seek $fidr $oldpos
  260. return $result
  261. }
  262. proc caspos {{position 1}} {
  263. checkactive
  264. lassign [seekpos $position] headerpos type
  265. return "Cassette header put to offset: $headerpos"
  266. }
  267. # Seek to the start of the n-th header and return both the
  268. # file-offset and the type of this header.
  269. proc seekpos {position} {
  270. if {![string is integer $position] || ($position <= 0)} {
  271. error "Expected a strict positive integer, but got $position."
  272. }
  273. variable fidr
  274. seek $fidr 0
  275. set i 0
  276. while {$i != $position} {
  277. set headerpos [seekheader]
  278. set type [readheader]
  279. if {$type != -1} {incr i}
  280. if {[eof $fidr]} {error "No entry $position in this cas file."}
  281. }
  282. seek $fidr $headerpos
  283. list $headerpos $type
  284. }
  285. proc casrun {{filename 1} {position 1}} {
  286. variable fidr
  287. variable bphread
  288. if {[string is integer $filename] && ($fidr ne "")} {
  289. # First argument is actually a position instead of a filename,
  290. # only works when there already is a cas file loaded.
  291. set position $filename
  292. catch {debug remove_bp $bphread} ;# often not set, so catch error
  293. } else {
  294. # Interpret 1st argument as a filename and load it.
  295. casload $filename
  296. }
  297. lassign [seekpos $position] headerpos type
  298. catch {carta eject}
  299. catch {cartb eject} ;# there are machines without slot-B
  300. reset
  301. set ::power on
  302. if {[machine_info type] eq "SVI"} {
  303. set bpaddr 0xfe94
  304. } else {
  305. set bpaddr 0xff07
  306. keymatrixdown 6 1 ;# press SHIFT
  307. }
  308. set bphread [debug set_bp ${bpaddr} {} [namespace code "typeload $type"]]
  309. return ""
  310. }
  311. proc typeload {type} {
  312. variable bphread
  313. catch {debug remove_bp $bphread} ;# often not set, so catch error
  314. if {[machine_info type] eq "SVI"} {
  315. set keybuf 0xfd8b
  316. set getpnt 0xfa1c
  317. set putpnt 0xfa1a
  318. } else {
  319. keymatrixup 6 1 ;# release SHIFT
  320. set keybuf 0xfbf0
  321. set getpnt 0xf3fa
  322. set putpnt 0xf3f8
  323. }
  324. set cmd [lindex [list "RUN\"CAS:\r" "BLOAD\"CAS:\",R\r" "CLOAD\rRUN\r"] $type]
  325. debug write_block memory $keybuf $cmd
  326. poke16 ${getpnt} $keybuf
  327. poke16 ${putpnt} [expr {$keybuf + [string length $cmd]}]
  328. }
  329. ######################################################
  330. proc tapedeck {args} {
  331. set isCas [expr {[string toupper [file extension [lindex $args end]]] eq ".CAS"}]
  332. if {$isCas} {
  333. switch [lindex $args 0] {
  334. eject {caseject}
  335. rewind {caspos 1}
  336. motorcontrol {}
  337. play {}
  338. record {}
  339. new {cassave [lindex $args 1]}
  340. insert {casload [lindex $args 1]}
  341. getpos {}
  342. getlength {}
  343. "" {}
  344. default {
  345. casload {*}$args
  346. # for SVI we can't use the trick below, so use typeload
  347. if {[expr {[machine_info type] eq "SVI" && $::autoruncassettes}]} {
  348. lassign [seekpos 1] headerpos type
  349. typeload $type
  350. }
  351. }
  352. }
  353. } else {
  354. caseject
  355. }
  356. if {[expr {[machine_info type] ne "SVI" || !$isCas}]} {
  357. # also insert the file in the normal openMSX cassetteplayer
  358. # to trigger the behaviour that happens when we do (e.g. autoload)
  359. # and of course to get normal behaviour for non-CAS files
  360. return [uplevel 1 [list interp invokehidden {} -global cassetteplayer] $args]
  361. }
  362. }
  363. ######################################################
  364. namespace export casload cassave caslist casrun caspos caseject
  365. } ;# namespace cashandler
  366. namespace import cashandler::*