diff --git a/.gitignore b/.gitignore index 1ebc23d36..622fd75a9 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ src/tests/*.list src/tests/*.map src/tests/*.label src/tests/*.lbl +src/tests/*.sym src/tools/*.list src/tools/*.map src/tools/*.label @@ -145,3 +146,7 @@ PASS.* # jenkins build artifacts JENKINS_BUILD_ID JENKINS_BUILD_VERSION + +# local hyppo DOS-trap exercisers, not part of the build +src/tests/hyppo_dos_traps_test.a65 +src/tests/hyppo_dos_util.a65 diff --git a/Makefile b/Makefile index 658fe0bd4..147e06173 100644 --- a/Makefile +++ b/Makefile @@ -1089,11 +1089,14 @@ $(BINDIR)/border.prg: $(SRCDIR)/border.a65 $(OPHIS_DEPEND) $(OPHIS) $(OPHISOPT) $< -l $(BINDIR)/border.list -m $*.map -o $(BINDIR)/border.prg # ============================ done moved, print-warn, clean-target +# --strict-segments: the fixed data areas at $BB00 and up wind * backwards, +# so code growing past them would otherwise only warn and quietly overwrite +# the tail of hyppo. $(BINDIR)/HICKUP.M65: $(ACME_DEPEND) $(wildcard $(SRCDIR)/hyppo/*.asm) $(SRCDIR)/version.asm - $(ACME) --cpu m65 --setpc 0x8000 -l src/hyppo/HICKUP.sym -r src/hyppo/HICKUP.rep -I $(SRCDIR)/hyppo -DDEBUG_HYPPO=$(DEBUG_HYPPO) $(SRCDIR)/hyppo/main.asm + $(ACME) --cpu m65 --strict-segments --setpc 0x8000 -l src/hyppo/HICKUP.sym -r src/hyppo/HICKUP.rep -I $(SRCDIR)/hyppo -DDEBUG_HYPPO=$(DEBUG_HYPPO) $(SRCDIR)/hyppo/main.asm $(BINDIR)/BRICKUP.M65: $(ACME_DEPEND) $(wildcard $(SRCDIR)/hyppo/*.asm) $(SRCDIR)/version.asm - $(ACME) --cpu m65 --setpc 0x8000 -l src/hyppo/HICKUP.sym -r src/hyppo/HICKUP.rep -I $(SRCDIR)/hyppo -DDEBUG_HYPPO=$(DEBUG_HYPPO) $(SRCDIR)/hyppo/joyflash.asm + $(ACME) --cpu m65 --strict-segments --setpc 0x8000 -l src/hyppo/HICKUP.sym -r src/hyppo/HICKUP.rep -I $(SRCDIR)/hyppo -DDEBUG_HYPPO=$(DEBUG_HYPPO) $(SRCDIR)/hyppo/joyflash.asm $(UTILDIR)/cpusim.prg: $(ACME_DEPEND) $(UTILDIR)/cpusim.asm $(ACME) --cpu m65 --setpc 0x8100 -l cpusim.sym -r cpusim.rep $(UTILDIR)/cpusim.asm diff --git a/src/hyppo/audiomix.asm b/src/hyppo/audiomix.asm index c4f88013b..10eafbd71 100644 --- a/src/hyppo/audiomix.asm +++ b/src/hyppo/audiomix.asm @@ -79,7 +79,7 @@ audiomix_set_sid_lr_coefficients: ldx #$22 jsr audiomix_set2coefficients ldx #$32 - jmp audiomix_set2coefficients + bra audiomix_set2coefficients audiomix_set_sid_rl_coefficients: ldx #$c2 @@ -97,7 +97,7 @@ audiomix_set_sid_rl_coefficients: ldx #$20 jsr audiomix_set2coefficients ldx #$30 - jmp audiomix_set2coefficients + bra audiomix_set2coefficients audio_set_stereo: ;; Left and right SID volume levels @@ -105,7 +105,7 @@ audio_set_stereo: lda #$be jsr audiomix_set_sid_lr_coefficients lda #$40 - jmp audiomix_set_sid_rl_coefficients + bra audiomix_set_sid_rl_coefficients audio_set_stereomirrored: ;; Left and right SID volume levels @@ -113,7 +113,7 @@ audio_set_stereomirrored: lda #$40 jsr audiomix_set_sid_lr_coefficients lda #$be - jmp audiomix_set_sid_rl_coefficients + bra audiomix_set_sid_rl_coefficients audiomix_setcoefficient: stx audiomix_addr @@ -144,4 +144,4 @@ audiomix_set4coefficients: audiomix_set2coefficients: jsr audiomix_setcoefficient inx - jmp audiomix_setcoefficient + bra audiomix_setcoefficient diff --git a/src/hyppo/constants.asm b/src/hyppo/constants.asm index f0338e0a1..7d9ceb495 100644 --- a/src/hyppo/constants.asm +++ b/src/hyppo/constants.asm @@ -39,6 +39,8 @@ dos_errorcode_file_exists = $8D dos_errorcode_directory_full = $8E dos_errorcode_double_attach = $8F + dos_errorcode_directory_not_empty = $90 + dos_errorcode_path_too_long = $91 dos_errorcode_eof = $FF ;; FAT directory entry constants diff --git a/src/hyppo/dos.asm b/src/hyppo/dos.asm index 582b6b4fd..38638b577 100644 --- a/src/hyppo/dos.asm +++ b/src/hyppo/dos.asm @@ -3,8 +3,6 @@ ;; Paul Gardner-Stephen, 2014-2019. ;; ---------------------------------------------------------------- */ - ;; XXX - Track down why 2nd and subsequent LFN blocks are not used. - dos_and_process_trap: ;; XXX - Machine is being updated to automatically disable IRQs on trapping @@ -34,32 +32,32 @@ dos_and_process_trap_table: ;; !16 trap_dos_getversion !16 trap_dos_getdefaultdrive - !16 trap_dos_getcurrentdrive ;; appears out-of-order (is far below) + !16 trap_dos_getcurrentdrive !16 trap_dos_selectdrive - !16 trap_dos_getdisksize ;; not currently implemented - !16 trap_dos_getcwd ;; not currently implemented + !16 trap_dos_getdisksize + !16 trap_dos_getcwd !16 trap_dos_chdir - !16 trap_dos_mkdir ;; not currently implemented + !16 trap_dos_mkdir ;; $10 - $1E ;; - !16 trap_dos_rmdir ;; not currently implemented + !16 trap_dos_rmdir !16 trap_dos_opendir !16 trap_dos_readdir !16 trap_dos_closedir !16 trap_dos_openfile !16 trap_dos_readfile !16 trap_dos_writefile - !16 trap_dos_mkfile ;; implementation started + !16 trap_dos_mkfile ;; $20 - $2E ;; !16 trap_dos_closefile !16 trap_dos_closeall - !16 trap_dos_seekfile ;; not currently implemented + !16 trap_dos_seekfile !16 trap_dos_rmfile - !16 trap_dos_fstat ;; implementation started - !16 trap_dos_rename ;; not currently implemented + !16 trap_dos_fstat + !16 trap_dos_rename !16 trap_dos_filedate ;; not currently implemented !16 trap_dos_setname @@ -122,48 +120,11 @@ trap_serial_monitor_write: sty hypervisor_write_char_to_serial_monitor jmp return_from_trap_with_success -;; ======================== - -trap_task_get_mapping: - jsr hypervisor_setup_copy_region - bcc @bad - ldy #5 -@copyloop: - lda hypervisor_maplohi,y - sta (" + bra return_from_trap_with_carry_flag +gds_havearea: - jmp return_from_trap_with_carry_flag + ldx dos_disk_table_offset + ldy #0 +gds_sector_count: + lda dos_disk_table + fs_sector_count,x + sta (" + sec + bra return_from_trap_with_carry_flag - +Checkpoint "trap_dos_setname" +;; ---- trap_dos_getcwd ---- +trap_dos_getcwd: + ;; Returns the current working directory as a path string + ;; (e.g. "/FOO/BAR"), resolved on demand by walking cwd up to + ;; root (no persistent path tracking - always correct + ;; regardless of how cwd got there). Each level's name is found + ;; by reading its own ".." entry for the parent cluster, then + ;; scanning the parent for the entry whose cluster matches. + ;; Assumes a well-formed filesystem - a cyclic ".." chain would + ;; loop forever, same trust level as the rest of Hyppo's FAT32 + ;; handling. + ;; + ;; A level contributes its long name where it has one and long + ;; names are enabled; otherwise its 8.3 short name. That makes a + ;; segment up to 64 characters rather than 12, so the path can + ;; outgrow the 255-byte area at about four levels of long names, + ;; and the call then fails with dos_errorcode_path_too_long + ;; rather than running back over the two bytes below. + ;; + ;; Y: MSB of destination area. + ;; Output, starting at $YY00: + ;; $00 byte current disk number + ;; $01 byte offset (within this same $YY00 area) of the + ;; null-terminated path string, e.g. "/FOO/BAR" + ;; + ;; Example: + ;; LDY #$80 ; destination page for the result + ;; LDA #$0A : STA $D640 : CLV : BCC error jsr hypervisor_setup_copy_region - bcc tdsnfailure + bcs gcwd_havearea + +Checkpoint "trap_dos_getcwd " + bra return_from_trap_with_carry_flag +gcwd_havearea: - ldx " jmp return_from_trap_with_failure -;; ======================== +gcwd_checkentry: + ;; Found it - prepend "/" and its name to the path buffer, + ;; growing backward from the cursor. dos_readdir has already put + ;; the long name in dos_dirent_longfilename if the entry has one + ;; and long names are enabled; otherwise it leaves the length at + ;; zero and the 8.3 name is derived instead. + lda dos_dirent_longfilename_length + bne gcwd_havesegment + jsr dos_derive_dotted_shortname + lda dos_dirent_longfilename_length +gcwd_havesegment: + sta " + sec + jmp return_from_trap_with_carry_flag - ;; BG: the below section seems never called from anywhere: suggest removal +gcwd_cwd_to_zptempv32: + ldx #3 +- lda dos_disk_cwd_cluster,x + sta " -;; ======================== + jmp return_from_trap_with_success_and_zero_accumulator -trap_dos_mkfile: +tdcd1: + +Checkpoint "trap_dos_chdir " - ;; XXX Filename must already be set. - ;; XXX Must be a file in the current directory only. - ;; XXX Can only create normal files, not directories - ;; (change attribute after). - ;; XXX Only supports 8.3 names for now. - ;; XXX Filenames without extension might still cause problems. - ;; XXX Allocates 512KB at a time, i.e., a full FAT sector's - ;; worth of clusters. - ;; XXX Allocates a contiguous block, so that D81s etc can - ;; be created, and guaranteed contiguous on the storage, - ;; so that they can be mounted. - ;; XXX Size of file specified in $ZZYYXX, i.e., limit of 16MB. - ;; XXX Doesn't handle full file systems (or ones without enough space - ;; free properly. Should check candidate cluster number is not too - ;; high, and abort if it is. + jmp generic_fail_from_error_code - ;; First, make sure the file doesn't already exist +;; ---- trap_dos_mkdir ---- + +trap_dos_mkdir: + ;; Creates a sub-directory in the current directory. + ;; + ;; Precondition: filename already set via hyppo_setname. + ;; Errors: dos_errorcode_file_exists if a file or sub-directory + ;; already exists with that name. + ;; + ;; Example: + ;; ; Set the new directory's name first via hyppo_setname (A=$2E). + ;; LDA #$0E : STA $D640 : CLV : BCC error + + ;; 1. Existence check (mirrors trap_dos_mkfile). jsr dos_findfile - bcc + - ;; File exists, so abort - clc + bcc mkdir_notfound lda #dos_errorcode_file_exists - sta dos_error_code - jmp return_from_trap_with_failure -+ - - ;; We need 1 FAT sector per 512KB of data. - ;; I.e., shift ZZ right by three bits to get number - ;; of empty FAT sectors we need to indicate sufficient space. - lda hypervisor_z - lsr - lsr - lsr - clc - adc #$01 - sta dos_scratch_byte_1 + +Checkpoint "trap_dos_mkdir " + jmp mkfile_fail_with_a +mkdir_notfound: + + ;; Build the short name and work out how many LFN pieces it needs. + jsr dos_analyze_name_or_fail + + ;; 2. Find a single free cluster for the new directory's own + ;; data. Unlike mkfile (which needs a whole *empty FAT sector* + ;; of contiguous clusters, since files need contiguous storage), + ;; a directory only ever needs exactly one cluster here, so this + ;; scans for the first individually-free cluster anywhere, + ;; rather than requiring 128 consecutive free ones. + ;; + ;; Must start sector-aligned (a multiple of 128), not at cluster + ;; 2: mkdir_found_free_cluster adds the scan's in-sector delta + ;; (0-127) straight onto this value, so starting at 2 would mislabel + ;; every found cluster as 2 higher than the one actually checked/ + ;; allocated. Clusters 0/1's reserved FAT entries are never zero + ;; on a valid volume, so including them in the scan is harmless. + jsr sd_map_sectorbuffer - ;; Now go looking for empty FAT sectors - ;; Start at cluster 128, and add 128 each time to step through - ;; them. - ;; This skips the first sector of FAT, which always has some used - ;; bits, and ensures we can allocate on a whole sector basis. - lda #128 + lda #0 sta <(zptempv32+0) - lda #$00 sta <(zptempv32+1) sta <(zptempv32+2) sta <(zptempv32+3) - ;; Initially 0 empty pages found - lda #0 - sta dos_scratch_byte_2 +mkdir_find_free_cluster_loop: + jsr dos_copy_zptempv32_and_read_fat_sector - jsr sd_map_sectorbuffer + ldx #0 + ldy #0 +mkdir_scan_firsthalf: + lda sd_sectorbuffer,y + ora sd_sectorbuffer+1,y + ora sd_sectorbuffer+2,y + ora sd_sectorbuffer+3,y + beq mkdir_found_free_cluster + inx + iny : iny : iny : iny + cpx #64 + bne mkdir_scan_firsthalf -find_empty_fat_page_loop: + ldy #0 +mkdir_scan_secondhalf: + lda sd_sectorbuffer+$100,y + ora sd_sectorbuffer+$101,y + ora sd_sectorbuffer+$102,y + ora sd_sectorbuffer+$103,y + beq mkdir_found_free_cluster + inx + iny : iny : iny : iny + cpx #128 + bne mkdir_scan_secondhalf - ldx #3 -- lda " + jmp generic_fail_from_error_code - jsr dos_file_read_current_sector +mkdir_havedirent: + jsr dos_write_lfn_and_shortentry - ;; Look for free dirent in first half of each sector. + ;; dirent: attributes/cluster/length, commit - shared with mkfile. + lda #fs_fat32_attribute_isdirectory ldx #0 - lda #$de - sta <(dos_scratch_vector+1) -- lda sd_sectorbuffer,x - cmp #$00 ;; vacant - beq available_dirent_slot - cmp #$e5 ;; deleted - beq available_dirent_slot - txa - adc #$20 - tax - bne - - inc <(dos_scratch_vector+1) -- lda sd_sectorbuffer,x - cmp #$00 ;; vacant - beq available_dirent_slot - cmp #$e5 ;; deleted - beq available_dirent_slot - txa - adc #$20 - tax - bne - + jsr dos_write_dirent_common - ;; No empty slots in this directory, so see if there any more sectors in - ;; this directory? - jsr dos_file_advance_to_next_sector - bcs empty_dirent_search_loop + +Checkpoint "trap_dos_mkdir " + jmp return_from_trap_with_success - ;; Directory is full, so return error - ;; XXX Later we should allow extending the directory by adding another cluster. - lda #dos_errorcode_directory_full - sta dos_error_code - clc +;; Zeroes the whole 512-byte sd_sectorbuffer. +dos_zero_sectorbuffer: + ldy #0 +- lda #0 + sta sd_sectorbuffer,y + sta sd_sectorbuffer+$100,y + iny + bne - rts -available_dirent_slot: - stx <(dos_scratch_vector+0) +;; ---- trap_dos_rmdir ---- - sec - rts +trap_dos_rmdir: + ;; Removes an empty sub-directory from the current directory. + ;; + ;; Precondition: filename already set via hyppo_setname. + ;; Errors: dos_errorcode_not_a_directory if the named entry isn't + ;; a directory; dos_errorcode_directory_not_empty if it contains + ;; anything besides '.' and '..'. + ;; + ;; Example: + ;; ; Set the target directory's name first via hyppo_setname (A=$2E). + ;; LDA #$10 : STA $D640 : CLV : BCC error + jsr dos_findfile + bcs rmdir_gotfile + +Checkpoint "trap_dos_rmdir " + jmp generic_fail_from_error_code -;; ======================== +rmdir_gotfile: + lda dos_dirent_type_and_attribs + and #fs_fat32_attribute_isdirectory + bne rmdir_is_a_directory + lda #dos_errorcode_not_a_directory + +Checkpoint "trap_dos_rmdir " + jmp mkfile_fail_with_a + +rmdir_is_a_directory: + ;; Temporarily point "cwd" at the target directory so the + ;; existing dos_opendir/dos_readdir machinery can scan it for + ;; emptiness. + jsr rmdir_save_cwd + + lda dos_dirent_cluster+0 + sta dos_disk_cwd_cluster+0 + lda dos_dirent_cluster+1 + sta dos_disk_cwd_cluster+1 + lda dos_dirent_cluster+2 + sta dos_disk_cwd_cluster+2 + lda dos_dirent_cluster+3 + sta dos_disk_cwd_cluster+3 -read_fat_sector_for_cluster: - jsr dos_cluster_to_fat_sector + jsr dos_opendir_save_current_fd + jsr dos_opendir + bcs rmdir_target_opened + jsr dos_opendir_restore_current_fd + jsr rmdir_restore_cwd + +Checkpoint "trap_dos_rmdir " + jmp return_from_trap_with_failure - ;; Now read the sector - ldx #3 -- lda dos_current_cluster,x - sta $d681,x - dex - bpl - - jmp sd_readsector +rmdir_target_opened: +rmdir_scanloop: + jsr dos_readdir + bcs rmdir_gotentry + + ;; readdir failed: per its documented error table, $85 invalid + ;; cluster means "read past the end of the directory" - i.e. + ;; genuinely empty (not dos_errorcode_eof, which is a different, + ;; file-read-oriented code). Anything else is a real error that + ;; must be propagated (not silently treated as "empty, ok to + ;; delete"). + lda dos_error_code + cmp #dos_errorcode_invalid_cluster + beq rmdir_scan_done + pha + jsr rmdir_close_and_restore + pla + +Checkpoint "trap_dos_rmdir " + jmp mkfile_fail_with_a + +rmdir_gotentry: + ;; dos_dirent_shortfilename is the raw, space-padded 11-byte + ;; on-disk short name, distinct from the reconstructed + ;; dos_dirent_longfilename: '.' is ". " and '..' is + ;; ".. ". + lda dos_dirent_shortfilename+0 + cmp #$2e + bne rmdir_notempty + lda dos_dirent_shortfilename+1 + cmp #$2e + beq rmdir_scanloop + cmp #$20 + beq rmdir_scanloop + +rmdir_notempty: + jsr rmdir_close_and_restore + lda #dos_errorcode_directory_not_empty + +Checkpoint "trap_dos_rmdir " + jmp mkfile_fail_with_a + +rmdir_scan_done: + jsr rmdir_close_and_restore + + ;; Empty - re-find the target in the (now-restored) parent + ;; directory. dos_requested_filename is untouched by the scan + ;; above (dos_readdir doesn't do name matching), so this locates + ;; the same entry and freshly populates dos_direntstart_* ready + ;; for removal. Marking a directory's own dirent deleted and + ;; freeing its cluster chain is mechanically identical to + ;; removing a file's, so this just reuses dos_rmfile directly. + jsr dos_findfile + bcs rmdir_relocated + +Checkpoint "trap_dos_rmdir " + jmp return_from_trap_with_failure -;; ======================== +rmdir_relocated: + jsr dos_rmfile + +Checkpoint "trap_dos_rmdir " + jmp return_from_trap_with_carry_flag + +rmdir_close_and_restore: + jsr dos_closefile_and_restore_current_fd + jmp rmdir_restore_cwd + +;; ---- trap_dos_opendir ---- trap_dos_opendir: @@ -726,6 +785,7 @@ trap_dos_opendir: ;; Something has gone wrong. Assume dos_opendir will ;; have set error code ;; +generic_fail_from_error_code: lda dos_error_code jmp return_from_trap_with_failure @@ -734,7 +794,7 @@ tdod1: ;; jmp return_from_trap_with_success_and_file_descriptor_in_a -;; ======================== +;; ---- trap_dos_readdir ---- trap_dos_readdir: @@ -785,81 +845,381 @@ tdrd2: tdrd1: +Checkpoint "trap_dos_readdir " - lda dos_error_code - jmp return_from_trap_with_failure + bra generic_fail_from_error_code -;; ======================== +;; ---- trap_dos_closedir ---- trap_dos_closedir: jmp trap_dos_closefile -;; ======================== +;; ---- trap_dos_openfile ---- + + +trap_dos_openfile: + + ;; Opens file in current dirent structure + ;; XXX - This means we must preserve the dirent struct when + ;; context-switching to avoid a race-condition + + jsr dos_openfile + bcc tdof1 + + +Checkpoint "trap_dos_openfile " + + jmp return_from_trap_with_success_and_file_descriptor_in_a + +tdof1: + +Checkpoint "trap_dos_openfile " + + bra generic_fail_from_error_code + +;; ---- trap_dos_readfile ---- trap_dos_readfile: jsr dos_readfile jmp return_from_trap_with_carry_flag +;; ---- trap_dos_writefile ---- + trap_dos_writefile: jsr dos_writefile jmp return_from_trap_with_carry_flag -trap_dos_rmfile: - jsr dos_rmfile - jmp return_from_trap_with_carry_flag +;; ---- trap_dos_mkfile ---- -trap_dos_fstat: - jsr dos_fstat - jmp return_from_trap_with_carry_flag +trap_dos_mkfile: -;; ======================== + ;; XXX Filename must already be set. + ;; XXX Must be a file in the current directory only. + ;; XXX Can only create normal files, not directories + ;; (change attribute after). + ;; XXX Size of file specified in $ZZYYXX, i.e., limit of 16MB. + ;; + ;; Searches in units of a whole FAT sector - 128 clusters - so + ;; that the space it finds is contiguous, which is what lets a + ;; D81 created here be mounted. The chain it then writes stops at + ;; the exact number of clusters the file needs, leaving the rest + ;; of the last sector free. -trap_dos_cdrootdir: - ldx hypervisor_x - jsr dos_cdroot - jmp return_from_trap_with_carry_flag + ;; First, make sure the file doesn't already exist + jsr dos_findfile + bcc + + ;; File exists, so abort + lda #dos_errorcode_file_exists + jmp mkfile_fail_with_a ++ -trap_dos_chdir: + ;; Build the short name and work out how many LFN pieces (if + ;; any) it needs, before allocating anything. + jsr dos_analyze_name_or_fail + + ;; How many clusters the file actually needs. + ;; + ;; The search below works a whole FAT sector at a time, but the + ;; chain written afterwards has to stop at exactly this many + ;; clusters. A chain longer than the file's recorded length is + ;; malformed FAT32 in its own right, and dos_checkimage rejects + ;; it outright, so a D81 created with a rounded-up chain could + ;; never be attached. + ;; + ;; clusters = ceil(ceil(size / 512) / sectors_per_cluster). + ;; Nested round-ups compose, so this is the same answer as + ;; dividing once by the cluster size in bytes, and it never has + ;; to hold anything wider than 16 bits or double + ;; sectors_per_cluster (which is a byte, and can be 128). +mkfile_count_clusters: + ldx dos_disk_table_offset - ;; Opens file in current dirent structure - ;; XXX - This means we must preserve the dirent struct when - ;; context-switching to avoid a race-condition + ;; 256-byte pages, rounded up: the size's own low byte only + ;; matters as "is there a part page as well", which cmp #1 + ;; turns into a carry. + lda hypervisor_x + cmp #1 + lda hypervisor_y + adc #0 + sta > 7 + rol + sta " + ;; Initially 0 empty pages found + lda #0 + sta " +find_empty_fat_page_loop: - lda dos_error_code - jmp return_from_trap_with_failure + jsr dos_copy_zptempv32_to_current_cluster -;; ======================== + jsr read_fat_sector_for_cluster + ;; Is the page empty + ldx #0 +- lda sd_sectorbuffer,x + bne + + lda sd_sectorbuffer+$100,x + bne + -trap_dos_openfile: + inx + bne - ++ - ;; Opens file in current dirent structure - ;; XXX - This means we must preserve the dirent struct when - ;; context-switching to avoid a race-condition + ;; Z=1 if FAT sector all unallocated, Z=0 otherwise + beq fat_sector_is_empty - jsr dos_openfile - bcc tdof1 + ;; Reset empty FAT sector counter + lda #0 + sta " +fat_sector_is_empty: + inc = cluster_count means we are + ;; past the end. Testing equality alone, as the check in + ;; dos_write.asm does, is not enough here because this walks 128 + ;; clusters at a stride and can step straight over the last one. + ;; + ;; dos_disk_table_offset is already valid: dos_findfile above and + ;; read_fat_sector_for_cluster in the loop both work through the + ;; current disk. + ldx dos_disk_table_offset + sec + lda <(zptempv32+0) + sbc dos_disk_table + fs_fat32_cluster_count + 0,x + lda <(zptempv32+1) + sbc dos_disk_table + fs_fat32_cluster_count + 1,x + lda <(zptempv32+2) + sbc dos_disk_table + fs_fat32_cluster_count + 2,x + lda <(zptempv32+3) + sbc dos_disk_table + fs_fat32_cluster_count + 3,x + bcc + + lda #dos_errorcode_no_space + jmp mkfile_fail_with_a ++ - jmp return_from_trap_with_success_and_file_descriptor_in_a + bra find_empty_fat_page_loop -tdof1: - +Checkpoint "trap_dos_openfile " +found_enough_contiguous_free_space: - lda dos_error_code - jmp return_from_trap_with_failure + ;; mkfile_start_cluster is the file's first cluster: the search + ;; noted it when this run of free FAT sectors began. -;; ======================== + ;; Find (N+1) consecutive free dirent slots: N LFN pieces + ;; (zptempv32b+0) plus the short entry itself. + jsr dos_find_n_free_dirents + bcs + + ;; Couldn't find enough free dirents, so return whatever error + ;; we have been indicated. + rts ++ + + ;; Save the directory sector's address - the dirent write is + ;; deferred until after the FAT-chain loop below, which reuses + ;; the same sector buffer for FAT reads/writes. + ldx #3 +- lda $d681,x + sta sd_sectorbuffer + sta (sd_sectorbuffer+$200) + bne mkfile_fat_entry + + ;; Sector full: write it to FAT1, mirror to FAT2, and carry the + ;; chain on into the next one. + jsr dos_write_sector_and_fat2_mirror + bra mkfile_fat_write_loop + +mkfile_fat_end_of_chain: + ;; $0FFFFFF8 marks the end of the file. zptempv32 is the running + ;; cluster number, but we are done with it - it gets reloaded + ;; from mkfile_start_cluster below. + lda #$F8 + sta <(zptempv32+0) + lda #$FF + sta <(zptempv32+1) + sta <(zptempv32+2) + lda #$0F + sta <(zptempv32+3) + jsr mkfile_put_fat_entry + jsr dos_write_sector_and_fat2_mirror + + ;; Restore the starting cluster and directory sector address, + ;; and re-read that sector fresh before writing the new dirent. + ldx #3 +- lda " - lda dos_error_code - jmp return_from_trap_with_failure + jmp generic_fail_from_error_code + +;; ---- trap_dos_seekfile ---- + ;; ======================== -trap_dos_findfile: +trap_dos_seekfile: + ;; Seeks to a given sector within the currently open file. + ;; + ;; Precondition: a file is currently open (hyppo_openfile) - same + ;; implicit "current file" convention as hyppo_readfile / + ;; hyppo_writefile (no FD register input; operates on whichever + ;; file was last opened). + ;; + ;; Inputs: X/Y/Z = 24-bit target sector number within the file + ;; (LSB/mid/MSB), matching hyppo_mkfile's $ZZYYXX convention. + ;; + ;; Narrow contract, not a general random-access primitive: FAT32 + ;; cluster chains are singly-linked, so this always walks forward + ;; from the start of the file, sector by sector. There is no + ;; per-file-descriptor length tracking, so this does not + ;; bounds-check against the file's real length - seeking past + ;; the end fails naturally when the cluster chain runs out, + ;; rather than with a dedicated end-of-file error. + ;; + ;; Example: + ;; ; Assume the file is already open (hyppo_openfile). + ;; LDX #$05 ; sector count, LSB + ;; LDY #$00 ; sector count, middle byte + ;; LDZ #$00 ; sector count, MSB + ;; LDA #$24 : STA $D640 : CLV : BCC error + + ;; Save the 24-bit target sector count in zptempv2/zptempp. + stx " jmp return_from_trap_with_carry_flag -;; ======================== +seekfile_atstart: +seekfile_loop: + lda " + jmp return_from_trap_with_carry_flag - jsr dos_findfirst - bcc + - jmp return_from_trap_with_success_and_file_descriptor_in_a -+ jmp return_from_trap_with_failure +seekfile_decrement: + lda " + jmp return_from_trap_with_success -trap_dos_findnext: +;; ---- trap_dos_rmfile ---- - jsr dos_findnext +trap_dos_rmfile: + jsr dos_rmfile jmp return_from_trap_with_carry_flag -;; ======================== - -trap_dos_geterrorcode: +;; ---- trap_dos_fstat ---- - lda dos_error_code - sta hypervisor_a +trap_dos_fstat: + ;; Returns raw dirent info (short name, attributes, length, dates) + ;; for the currently-located file. + ;; + ;; Precondition: hyppo_setname + hyppo_findfile first, to make the + ;; target the "current" match. + ;; Y: MSB of destination area for the raw 32-byte dirent. + ;; + ;; Example: + ;; ; Assume setname+findfile already located the target. + ;; LDY #$80 ; destination page for the result + ;; LDA #$28 : STA $D640 : CLV : BCC error + jsr dos_fstat + +Checkpoint "trap_dos_fstat " + jmp return_from_trap_with_carry_flag -!if DEBUG_HYPPO { - tax ;; convert .X to char-representation for display - jsr checkpoint_bytetohex ;; returns: .X and .Y (Y is MSB, X is LSB, print YX) - sty tdgec1+0 - stx tdgec1+1 +;; ---- trap_dos_rename ---- - jsr checkpoint - !8 0 - !text "dos_geterrorcode <=$" -tdgec1: !text "%%>" - !8 0 -} +trap_dos_rename: + ;; Renames an already-open file, or the currently-open directory + ;; (an FD from hyppo_opendir - not an arbitrary named + ;; subdirectory). A new name that fits 8.3 is written in place; + ;; one that needs VFAT long-name pieces is relocated to fresh + ;; free slots elsewhere in the directory instead (see + ;; rename_lfn below), reusing the same LFN writer mkfile/mkdir + ;; use. Either way the entry's attributes/dates/cluster/length + ;; are preserved untouched - only the name changes. + ;; + ;; Precondition: like hyppo_writefile, this operates on an + ;; already-open file descriptor: X = the FD (from hyppo_openfile, + ;; or hyppo_opendir to rename the directory itself). Y = page of + ;; a null-terminated buffer holding the new name (same + ;; convention hyppo_setname itself uses for its input). For a + ;; FILE FD specifically, cwd must still be whatever directory + ;; it was opened from - files carry no ".." of their own, so + ;; (unlike a directory FD) there's no way to re-derive their + ;; parent if cwd has since changed. + ;; + ;; Errors: dos_errorcode_invalid_file_descriptor if X isn't an + ;; open FD; dos_errorcode_file_exists if the new name already + ;; exists; dos_errorcode_name_too_long if the new name is empty + ;; or too long to ever reassemble (more than 5 LFN pieces, i.e. + ;; over 65 characters); dos_errorcode_directory_full if it needs + ;; LFN pieces and there's no room left for them; dos_errorcode_ + ;; file_not_found if asked to rename the root directory (it has + ;; no entry of its own to rename), or if the FD's own entry can + ;; no longer be located (e.g. a file whose cwd precondition + ;; above wasn't met). + ;; + ;; Example: + ;; ; Assume the file is already open (hyppo_openfile) with its FD in X. + ;; LDX openfile_fd + ;; LDY #$80 ; page of a nul-terminated new-name buffer + ;; LDA #$2A : STA $D640 : CLV : BCC error + + ;; Pull the new name out of userland into dos_requested_filename, + ;; via the exact same safety-checked path hyppo_setname uses. + jsr dos_setname_from_userspace + lbcc rename_fail_alreadyset +rename_have_name: + + ;; Save the real cwd - restored before every return below (we + ;; temporarily repoint it at the parent directory we need to + ;; search, reusing the existing dos_opendir/dos_readdir + ;; machinery on it, same trick hyppo_getcwd/hyppo_rmdir use). + ldx #3 +- lda dos_disk_cwd_cluster,x + sta " + jmp mkfile_fail_with_a +rename_not_root: + jsr dos_find_parent_of_cluster + +rename_have_parent: + jsr dos_find_dirent_in_cwd_by_cluster + bcs rename_found + pha ;; rename_restore_cwd clobbers A - save the error code + jsr rename_restore_cwd + pla + +Checkpoint "trap_dos_rename " + jmp mkfile_fail_with_a -;; ======================== +rename_found: + ;; Protect our own dirent position before the destination check + ;; below runs its own findfile/readdir scan (which would + ;; otherwise clobber dos_direntstart_*). cwd is still pointed + ;; at our parent, which is exactly where the new name must not + ;; already exist. + jsr dos_save_direntstart_to_zptemp -trap_dos_d81attach1: + jsr dos_findfile + bcc rename_destfree + jsr rename_restore_cwd + lda #dos_errorcode_file_exists + +Checkpoint "trap_dos_rename " + jmp mkfile_fail_with_a - +Checkpoint "trap_dos_d81attach1" +rename_destfree: + ;; Restore the saved position and locate its sector. + jsr dos_restore_direntstart_from_zptemp + jsr dos_goto_direntstart_and_point_scratch_vector - ldx #$01 - jsr dos_attach - jmp return_from_trap_with_carry_flag + jsr dos_analyze_name_for_dirent + bcc rename_shortcopy_setup -;; ======================== + ;; Carry set: either a valid LFN piece count (relocate), or + ;; $ff (name unusable - too long to ever reassemble, or empty). + lda " + jmp return_from_trap_with_success - +Checkpoint "trap_dos_d81detach" +rename_name_too_long: + jsr rename_restore_cwd + lda #dos_errorcode_name_too_long + +Checkpoint "trap_dos_rename " + jmp mkfile_fail_with_a - ldx #%11000010 ;; detach both drives, don't attach real drives - jsr dos_attach +rename_fail_alreadyset: + +Checkpoint "trap_dos_rename " + jmp return_from_trap_with_failure - jmp return_from_trap_with_carry_flag +;; New name doesn't fit 8.3 - relocate to N+1 fresh consecutive slots +;; elsewhere in the directory. dos_write_lfn_and_shortentry only fills +;; in the name fields (offsets 0-10); attributes/dates/cluster/length +;; (offsets 11-31) are saved from the old entry and copied verbatim. +rename_lfn: + ;; dos_analyze_name_for_dirent (just called) may have gone + ;; through dos_shortname_exists to check the new tilde-numbered + ;; short name for collisions, which does its own directory scan + ;; and clobbers the shared SD sector buffer dos_scratch_vector + ;; still points into. dos_direntstart_* itself is untouched by + ;; that scan, so re-derive dos_scratch_vector from it before + ;; reading anything through it. Direct variant - must not + ;; disturb this FD's own currentcluster/sectorincluster (the + ;; caller may keep reading/writing this FD after the rename). + jsr dos_goto_direntstart_direct_and_point_scratch_vector + + ldy #fs_fat32_dirent_offset_attributes + ldx #0 +rlfn_save_loop: + lda (" + jmp return_from_trap_with_failure + +rename_lfn_havedirent: + jsr dos_write_lfn_and_shortentry + + ldy #fs_fat32_dirent_offset_attributes + ldx #0 +rlfn_restore_loop: + lda dos_rename_saved_dirent,x + sta (" + !8 0 +} + + jmp return_from_trap_with_success + +;; ---- trap_dos_setup_transfer_area ---- + +trap_dos_setup_transfer_area: + + jsr hypervisor_setup_copy_region + + jmp return_from_trap_with_carry_flag + +;; ---- trap_dos_cdrootdir ---- + +trap_dos_cdrootdir: ldx hypervisor_x + jsr dos_cdroot + jmp return_from_trap_with_carry_flag + +;; ---- trap_dos_loadfile_attic ---- + +trap_dos_loadfile_attic: + lda #$08 ; Set address to $8xxxxxx to access attic RAM + !8 $2c ; BIT $xxxx to skip lda #$00 below + ;; FALL THROUGH + +trap_dos_loadfile: + + ;; Only allow loading into lower 16MB to avoid possibility of writing + ;; over hypervisor + ;; + lda #$00 + sta <(dos_file_loadaddress+3) + + lda hypervisor_x + sta 0 writes hypervisor_x/y/z (mkfile). +dos_write_dirent_common: + ldy #fs_fat32_dirent_offset_attributes + sta (= 9 + lda = 4 -dos_clear_filedescriptors: + lda <(zptempv2+1) + jsr ran_copy_base_chars ;; base_len already <= 8 here + lda done + inc last piece + sec + sbc #13 + bra ran_tilde_piece_loop +ran_tilde_piece_lastdone: + lda #0 + bra ran_tilde_piece_loop +ran_tilde_piece_done: sec rts -;; ======================== +;; A = number of base chars to copy. Copies+uppercases into +;; dos_dirent_shortfilename, leaves X = count. +ran_copy_base_chars: + sta <(zptempv2+1) ;; loop bound + ldx #0 +ran_copy_base_chars_loop: + cpx <(zptempv2+1) + beq ran_copy_base_chars_done + lda dos_requested_filename,x + jsr toupper + sta dos_dirent_shortfilename,x + inx + bra ran_copy_base_chars_loop +ran_copy_base_chars_done: + rts + +;; A = number of extension chars to copy (0 is a valid no-op). +;; Copies+uppercases into dos_dirent_shortfilename+8. +ran_copy_ext_chars: + sta must be LFN + bra drce_longname ;; MATCH -> must be LFN drce_cont0: tya ;; from safe keeping @@ -2642,7 +4337,7 @@ drce_cont0: and #$08 cmp #$08 ;; %00001000 Vol-ID bne drce_cont2 - jmp drce_cont_next_part ; Ignore it + bra drce_cont_next_part ; Ignore it drce_cont2: tya ;; from safe keeping @@ -2668,12 +4363,12 @@ drce_cont2: drce_cont3: ;; was not hidden/system, or Vol-ID, or LFN, or was special case "..", ;; so we process this entry regardless of if read-only (bit0) or not - jmp drce_normalrecord + bra drce_normalrecord drce_ignore: ;; Ignore hidden/system files for now ;; XXX We should have a flag to enable/disable this behaviour - jmp drce_cont_next_part + bra drce_cont_next_part dotdotshortname: !text ".. " @@ -2683,21 +4378,19 @@ dotdotshortname: drce_longname: disable_lfn_byte: - jmp drce_cont_next_part + jmp drce_cont_next_part ;; First byte (jmp) self modifying fom syspart.asm. JMP = LFN disabled, BIT= LFN Enabled ;; make sure long entry type is "filename" (=$00) ;; ldy #fs_fat32_dirent_offset_lfn_type lda (msg_clusternumber jsr printmessage ldy #$00 - ldz dos_current_cluster+3 + ldz $0200 - lda dos_bytes_remaining+2 - ora dos_bytes_remaining+3 + lda regular data file - lda dos_file_descriptors+2,y - and dos_file_descriptors+1,y - cmp #$ff - bne drf_ok ; not $ffff -> regular data file - lda dos_file_descriptors,y - and #$f0 - cmp #$f0 - beq drf_eof ; $0ffffff? -> eof marker - -drf_ok: - jsr drf_clearwriteandincreasesector - sec - rts - -drf_eof: + pha ;; 0=ok, 1=eof - remember across the clearwrite call jsr drf_clearwriteandincreasesector + pla + beq drf_ok2 clc rts +drf_ok2: + sec + rts drf_fail: clc rts drf_clearwriteandincreasesector - lda dos_scratch_byte_1 + lda $6200 + sta $0ffd + sta freeze_region_dmalist + sta $d701 + lda #freeze_region_dmalist - sta $d701 - lda #freeze_region_dmalist - sta $d701 - lda #$6200 - sta $0FFD - sta $1000 - sta $2640 - sta zptempv32+1 - lda #<$0FFD - sta zptempv32+2 - lda #>$0FFD - sta zptempv32+3 - - ;; First, make sure the read pointer is at the start of the thumbnail - ldz #$00 - ;; Then advance pointer address to $D641 - lda [$6200 - sta $0ffd - sta freeze_region_dmalist - sta $d701 - lda #$6e11 - sta $0ffd - sta msg_hyppohelpfirst jsr printmessage - jmp first_boot_flag_instruction + bra first_boot_flag_instruction not_first_boot_message: ldx #') to skip -; over the loop and then comfortably step through early -; hypervisor code. + ;; add a test if the ESC key is held down + ;; if so, make an endless loop so that a person debugging + ;; and turn trace mode on, move the pc (with 'g') to skip + ;; over the loop and then comfortably step through early + ;; hypervisor code. ldx #$ff ;; make a few attempts are reading keyscan early @earlyscan: @@ -795,7 +775,7 @@ morewaiting: plx - jmp tryreadmbr + bra tryreadmbr trybus0: dex bne morewaiting @@ -839,8 +819,7 @@ tryreadmbr: jsr scankeyboard bcs nokey2 cmp #$20 - bne nokey2 - jmp utility_menu + lbeq utility_menu nokey2: ;; Oops, cant read MBR @@ -859,7 +838,7 @@ nokey2: ;; +Checkpoint "re-try reading MBR of sdcard" - jmp tryreadmbr + bra tryreadmbr ;; ======================== @@ -895,8 +874,7 @@ gotmbr: ;; If we have no disks, offer the utility menu lda dos_disk_count - bne @thereIsADisk - jmp utility_menu + lbeq utility_menu @thereIsADisk: ;; Go to root directory on default disk @@ -1371,7 +1349,7 @@ loadrom: jsr attempt_loadcharrom bcs loadedcharromok - jmp loadc65rom + bra loadc65rom ;; ======================== @@ -1444,7 +1422,7 @@ loadedok: cmp #$00 bne @romFileNotTooShort @romFileIsTooShort: - jmp romfiletooshort + bra romfiletooshort @romFileNotTooShort: cmp #$01 beq @romFileIsTooShort @@ -1453,7 +1431,7 @@ loadedok: lda file_pagesread beq @romFileNotTooLong @romFileIsTooLong: - jmp romfiletoolong + bra romfiletoolong @romFileNotTooLong: ;; the loaded ROM was OK in size @@ -1472,7 +1450,7 @@ loadedok: lda # carry + lda #$4c ; JMP $xxxx opcode (LFN support disabled) + bcc @setlfn + lda #$2c ; BIT $xxxx opcode (LFN support enabled) +@setlfn: sta disable_lfn_byte -@nolfn: ;; Copy MAC address ldx #$05 maccopy: diff --git a/src/hyppo/targetsetup.asm b/src/hyppo/targetsetup.asm index 162b7cacc..7bde90051 100644 --- a/src/hyppo/targetsetup.asm +++ b/src/hyppo/targetsetup.asm @@ -68,7 +68,7 @@ mps3_loop: dex bne - - jmp mps3_loop + bra mps3_loop i2c_job_delay: phx @@ -124,7 +124,7 @@ mps_loop: bne - - jmp mps_loop + bra mps_loop megaphone_r1_i2c_settings: diff --git a/src/hyppo/task.asm b/src/hyppo/task.asm index baa9890e4..ff1e20df3 100644 --- a/src/hyppo/task.asm +++ b/src/hyppo/task.asm @@ -168,8 +168,7 @@ task_asblankslate: jsr task_new_processcontrolblock - jsr task_set_c64_memorymap - rts + jmp task_set_c64_memorymap task_set_as_system_task: ;; Task ID is reserved for the hypervisor and its helpers, and prevents freezing @@ -238,7 +237,7 @@ ethernet_remote_trap: lda #>$8000 sta hypervisor_pch - jmp safe_exit_to_loaded_program + bra safe_exit_to_loaded_program unstable_illegal_opcode_trap: kill_opcode_trap: @@ -261,9 +260,14 @@ restore_press_trap: non_hypervisor_task: + ;; Carry the task's current directory into the process descriptor + ;; before the freeze writes that page out. + jsr dos_save_cwd_to_task + ;; Clear colour RAM at $DC00 flag, as it causes no end of trouble lda #$01 trb $D030 + ;; and DMA audio lda #$00 sta $d711 diff --git a/src/tools/hyppotest.c b/src/tools/hyppotest.c index 935043ea1..90e346d7a 100644 --- a/src/tools/hyppotest.c +++ b/src/tools/hyppotest.c @@ -198,7 +198,7 @@ void disassemble_rel8(FILE *f, struct instruction_log *log) void disassemble_rel16(FILE *f, struct instruction_log *log) { - fprintf(f, "$%04X", log->pc + 2 + rel16_delta(log->bytes[1] + (log->bytes[1] << 8))); + fprintf(f, "$%04X", log->pc + 3 + rel16_delta(log->bytes[1] + (log->bytes[2] << 8))); } void disassemble_imm(FILE *f, struct instruction_log *log) @@ -607,6 +607,10 @@ void disassemble_instruction(FILE *f, struct instruction_log *log) fprintf(f, "EOR "); disassemble_izpz(f, log); break; + case 0x53: + fprintf(f, "BVC "); + disassemble_rel16(f, log); + break; case 0x56: fprintf(f, "LSR "); disassemble_zpx(f, log); @@ -735,6 +739,10 @@ void disassemble_instruction(FILE *f, struct instruction_log *log) fprintf(f, "ADC "); disassemble_izpz(f, log); break; + case 0x73: + fprintf(f, "BVS "); + disassemble_rel16(f, log); + break; case 0x74: fprintf(f, "STZ "); disassemble_zpx(f, log); @@ -966,6 +974,10 @@ void disassemble_instruction(FILE *f, struct instruction_log *log) fprintf(f, "LDA "); disassemble_izpz(f, log); break; + case 0xb3: + fprintf(f, "BCS "); + disassemble_rel16(f, log); + break; case 0xb4: fprintf(f, "LDY "); disassemble_zpx(f, log); @@ -1024,6 +1036,10 @@ void disassemble_instruction(FILE *f, struct instruction_log *log) fprintf(f, "CMP "); disassemble_zp(f, log); break; + case 0xC3: + fprintf(f, "DEW "); + disassemble_zp(f, log); + break; case 0xC6: fprintf(f, "DEC "); disassemble_zp(f, log); @@ -1123,6 +1139,10 @@ void disassemble_instruction(FILE *f, struct instruction_log *log) fprintf(f, "SBC "); disassemble_zp(f, log); break; + case 0xE3: + fprintf(f, "INW "); + disassemble_zp(f, log); + break; case 0xE6: fprintf(f, "INC "); disassemble_zp(f, log); @@ -1138,6 +1158,10 @@ void disassemble_instruction(FILE *f, struct instruction_log *log) fprintf(f, "SBC "); disassemble_imm(f, log); break; + case 0xd3: + fprintf(f, "BNE "); + disassemble_rel16(f, log); + break; case 0xea: fprintf(f, "EOM"); break; @@ -1380,9 +1404,14 @@ void cpu_log_reset(void) void cpu_stash_ram(void) { - // Remember the RAM contents before calling a routine + // Remember the RAM contents before calling a routine. Colour RAM and + // $FFDxxxx go in too: compare_ram_contents() checks them, so leaving + // them out of the snapshot meant every "check ram" reported the whole + // of colour RAM as changed, whatever the test had actually done. bcopy(chipram, chipram_expected, CHIPRAM_SIZE); bcopy(hypporam, hypporam_expected, HYPPORAM_SIZE); + bcopy(colourram, colourram_expected, COLOURRAM_SIZE); + bcopy(ffdram, ffdram_expected, sizeof(ffdram)); } unsigned int addr_to_28bit(struct cpu *cpu, unsigned int addr, int writeP) @@ -2439,7 +2468,12 @@ unsigned char stack_pop_ext(struct cpu *cpu, unsigned short amount, struct instr cpu->stack_underflow = true; cpu->regs.sp = new_sp; } - log->pop_blame[log->pops++] = memory_blame(cpu, cpu->regs.sp); + // Only the first two entries are ever read back (to blame whoever + // pushed the value), and log->pops is never reset, so a log entry + // reused by a long-running loop would run off the end of the array + // and corrupt the heap. + if (log->pops < MAX_POPS) + log->pop_blame[log->pops++] = memory_blame(cpu, cpu->regs.sp); return read_memory(cpu, cpu->regs.sp); } @@ -2602,7 +2636,7 @@ bool execute_instruction(struct cpu *cpu, struct instruction_log *log) if (cpu->regs.flags & FLAG_N) cpu->regs.pc += 3; else - cpu->regs.pc += 2 + rel16_delta(log->bytes[1]); + cpu->regs.pc += 3 + rel16_delta(log->bytes[1] + (log->bytes[2] << 8)); break; case 0x14: // TRB $xx log->len = 2; @@ -2831,7 +2865,7 @@ bool execute_instruction(struct cpu *cpu, struct instruction_log *log) if (!(cpu->regs.flags & FLAG_N)) cpu->regs.pc += 3; else - cpu->regs.pc += 2 + rel16_delta(log->bytes[1]); + cpu->regs.pc += 3 + rel16_delta(log->bytes[1] + (log->bytes[2] << 8)); break; case 0x34: // BIT $xx,X log->len = 2; @@ -3029,6 +3063,13 @@ bool execute_instruction(struct cpu *cpu, struct instruction_log *log) log->len = 2; cpu->regs.pc += 2; break; + case 0x53: // BVC $rrrr + log->len = 3; + if (!(cpu->regs.flags & FLAG_V)) + cpu->regs.pc += 3 + rel16_delta(log->bytes[1] + (log->bytes[2] << 8)); + else + cpu->regs.pc += 3; + break; case 0x55: // EOR $nn,X v = read_memory(cpu, addr_zpx(cpu, log)); v ^= cpu->regs.a; @@ -3234,6 +3275,13 @@ bool execute_instruction(struct cpu *cpu, struct instruction_log *log) log->len = 2; cpu->regs.pc += 2; break; + case 0x73: // BVS $rrrr + log->len = 3; + if ((cpu->regs.flags & FLAG_V)) + cpu->regs.pc += 3 + rel16_delta(log->bytes[1] + (log->bytes[2] << 8)); + else + cpu->regs.pc += 3; + break; case 0x74: // STZ $xx,X log->len = 2; cpu->regs.pc += 2; @@ -3322,7 +3370,7 @@ bool execute_instruction(struct cpu *cpu, struct instruction_log *log) break; case 0x83: // BRA $rrrr log->len = 3; - cpu->regs.pc += 2 + rel16_delta(log->bytes[1]); + cpu->regs.pc += 3 + rel16_delta(log->bytes[1] + (log->bytes[2] << 8)); break; case 0x84: // STY $xx log->len = 2; @@ -3597,6 +3645,13 @@ bool execute_instruction(struct cpu *cpu, struct instruction_log *log) cpu->regs.a = read_memory(cpu, addr_izpz(cpu, log)); update_nz(cpu->regs.a); break; + case 0xb3: // BCS $rrrr + log->len = 3; + if ((cpu->regs.flags & FLAG_C)) + cpu->regs.pc += 3 + rel16_delta(log->bytes[1] + (log->bytes[2] << 8)); + else + cpu->regs.pc += 3; + break; case 0xb4: // LDY $xx,X log->len = 2; cpu->regs.pc += 2; @@ -3688,6 +3743,23 @@ bool execute_instruction(struct cpu *cpu, struct instruction_log *log) log->len = 2; cpu->regs.pc += 2; break; + case 0xC3: // DEW $nn + case 0xE3: // INW $nn + // 16-bit read-modify-write of a zero page word. N and Z come from + // the whole 16-bit result rather than from either byte, which is + // what makes "dew" followed by "beq" a 16-bit countdown. + log->len = 2; + cpu->regs.pc += 2; + { + unsigned int zpw = addr_zp(cpu, log); + unsigned int w = read_memory(cpu, zpw) | (read_memory(cpu, zpw + 1) << 8); + w = (w + (log->bytes[0] == 0xE3 ? 1 : -1)) & 0xffff; + MEM_WRITE16(cpu, zpw, w & 0xff); + MEM_WRITE16(cpu, zpw + 1, w >> 8); + cpu->regs.flag_n = (w & 0x8000) ? 1 : 0; + cpu->regs.flag_z = (w == 0); + } + break; case 0xC6: // DEC $xx log->len = 2; cpu->regs.pc += 2; @@ -3836,6 +3908,13 @@ bool execute_instruction(struct cpu *cpu, struct instruction_log *log) log->len = 2; cpu->regs.pc += 2; break; + case 0xd3: // BNE $rrrr + log->len = 3; + if (!(cpu->regs.flags & FLAG_Z)) + cpu->regs.pc += 3 + rel16_delta(log->bytes[1] + (log->bytes[2] << 8)); + else + cpu->regs.pc += 3; + break; case 0xe1: // SBC ($nn,X) sbc(cpu, read_memory(cpu, addr_izpx(cpu, log))); log->len = 2; @@ -3930,7 +4009,7 @@ bool execute_instruction(struct cpu *cpu, struct instruction_log *log) case 0xf3: // BEQ $rrrr log->len = 3; if (cpu->regs.flags & FLAG_Z) - cpu->regs.pc += 3 + rel16_delta(log->bytes[1]); + cpu->regs.pc += 3 + rel16_delta(log->bytes[1] + (log->bytes[2] << 8)); else cpu->regs.pc += 3; break;