-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodeprobe.asm
More file actions
4684 lines (3441 loc) · 101 KB
/
Copy pathcodeprobe.asm
File metadata and controls
4684 lines (3441 loc) · 101 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//*******************************************************************************
//
// Project: Code Probe
// Version: 2.1 (C64 Version)
// Release Date: 1990
// Last Updated: 2026-04-29 (Prepared for Kick Assembler)
// Author: Rohin Gosling
//
// DESCRIPTION:
//
// A lightweight machine language monitor for the Commodore 64.
//
// Code Probe provides a software-based alternative to hardware cartridge
// based monitors, allowing users to inspect and modify memory, manage CPU
// registers, load and save files, and execute machine language programs.
//
// Code Probe was originally written for the Commodore VIC-20, and was later
// ported to the C64 with some updates and improvements.
//
// USAGE:
//
// Load Code Probe into memory at $C000.
//
// LOAD "CODEPROBE", 8, 1
// SYS 49152 or SYS 12*4096 (Which ever you prefer)
//
//
// COMMAND SUMMARY:
//
// D <start> <end> - Hex dump memory range.
// A <address> - Enter alter mode to write bytes to memory.
// R - Display registers.
// R <reg> <value> - Set register to value and display.
// RF - Display registers and flag bit-view.
// RF <flag> <bit> - Set flag to bit value and display.
// F <address> <count> <value> - Fill memory range with byte value.
// T <source> <count> <dest> - Transfer memory range to new location.
// S <device> <filename> - Save memory range to file.
// L <device> <filename> - Load file into memory.
// G <address> - Execute machine language program at address.
// CLS - Clear screen.
// EXIT - Exit Code Probe and return to BASIC.
//
// CHANGE LOG:
//
// Code Probe 1.0 (VIC-20, 1988)
//
// - Initial release.
//
// Code Probe 2.0 (C64, 1990)
//
// - Ported to C64.
// - Multiple bug fixes.
// - Added error checking and messages.
// - Added support for disk drive.
// - Updated register and flag display to be more readable on a 40 column screen.
// - Added ability to set registers and flags.
//
// Code Probe 2.1 (C64, 2026)
//
// - Restoration project to recover assembly language listing from
// disassembled machine code on the original disk image, and prepare it
// for use with Kick Assembler and VS Code. Original assembly listing
// existed as hand-assembled handwritten pages, long lost to time. Much
// of the original Code Probe was written using a combination of BASIC
// machine language loaders, and older versions of itself.
// - Since it's 2026, I used Claude Code to analyze and format the
// disassembled code, identify and fix old bugs, and aid in the
// reconstruction of comments.
//
// GYMNASTICS:
//
// The original VIC-20 version of Code-Probe was super minimal and served
// its purpose well enough. Porting Code-Probe to the C64 opened up
// opportunities to significantly expand its feature set. However, in spite
// of the larger memory budget available on the C64, it was still quite a
// challenge to get everything to fit in the C000–CFFF (4KB) memory range.
// For the sake of academic interest and/or entertainment, here are some of
// the things I did to make it all fit. All together, the combined savings
// of the tricks below free up around 450 to 550 bytes.
//
// - Code structure and algorithmic tricks
//
// - Tail-call optimization:
// print_hex_word falls through into print_hex_byte, which jmps to
// print_nibble instead of jsr+rts. Saves 2–3 bytes per call site.
//
// - Fall-through print chains:
// print_indent falls through to print_spaces; print_newline tail-calls
// KERNAL_CHROUT rather than wrapping it.
//
// - Table-driven command dispatch:
// dispatch_command walks a 4-byte-per-entry command_table
// (name pointer + handler pointer) and invokes handlers via an indirect
// jmp through ZP_PTR_1, replacing a long chain of inline compares.
//
// - Inverted-branch + jmp pattern:
// Large handlers (e.g. cmd_s) use bcs skip/jmp error to reach error
// labels that exceed the ±127 byte relative branch range, instead of
// duplicating error code closer to each check.
//
// - Shared subroutines and handlers:
//
// - Single error handler (cmd_s_error):
// Every command's parse-error path jumps to one handler instead of
// carrying its own.
//
// - Unified hex tokenizer:
// One parse_hex_byte / parse_hex_word pair, writing to a common
// hex_parse_result buffer, serves the D, A, R, RF, F, T, G, L,
// and S commands.
//
// - Shared 16-bit arithmetic:
// compare_16, add_16, subtract_16 (each <10 bytes) are reused by the
// D, F, and T commands, instead of inline math.
//
// - Data layout tricks:
//
// - Contiguous shadow-register block:
// The A, X, Y, SP, PCL, PCH, P, and IO registers are stored as one
// 8-byte array, so print_registers and cmd_r iterate with indexed
// addressing instead of eight separate code paths.
//
// - Parallel register metadata table:
// register_name_table packs name-pointer + shadow offset + width
// flag per entry, so cmd_r pulls all three via a single indexed read
// pair rather than a switch.
//
// - Precomputed flag bit table:
// flag_bit_table holds the 8 status-flag bitmasks so RF can mask via
// lda flag_bit_table,x instead of per-flag logic.
//
// - Multiplexed I/O buffers:
// filename_buffer, drive_filename, file_io_device, file_io_name_length
// are shared between S and L (only one runs at a time). Saves ~50 bytes.
//
// - Shared I/O label strings:
// the S and L commands both print through io_label_address rather than
// each carrying its own copy.
//
// - Shared overflow flag byte:
// The F and T commands both use a single byte to signal clamped ranges
// instead of separate booleans plus branches.
//
// - Zero page and KERNAL:
//
// - Two global ZP pointers:
// ZP_PTR_1 ($FB/$FC) and ZP_PTR_2 ($FD/$FE) serve every command's
// parsing/string/copy needs instead of per-feature pointer pairs.
//
// - Leaning on KERNAL:
// CHROUT, CHRIN, GETIN, SETLFS, SETNAM, OPEN, CLOSE, CHKOUT, CLRCHN,
// and LOAD are all borrowed rather than reimplemented;
// KERNAL is left banked in precisely so $C000 has the whole 4 KB free.
//
// - Implicit register preservation across KERNAL calls:
// For example, KERNAL_LOAD is entered with A=0 already left behind
// by SETLFS, avoiding a redundant lda #$00.
//
//*******************************************************************************
//==============================================================================
// Constants
//==============================================================================
// KERNAL routines.
.const KERNAL_CHROUT = $FFD2 // Output character to current device
.const KERNAL_CHRIN = $FFCF // Input character from current device
.const KERNAL_GETIN = $FFE4 // Get character from keyboard buffer
.const KERNAL_SETLFS = $FFBA // Set logical file parameters
.const KERNAL_SETNAM = $FFBD // Set file name
.const KERNAL_OPEN = $FFC0 // Open logical file
.const KERNAL_CLOSE = $FFC3 // Close logical file
.const KERNAL_CHKOUT = $FFC9 // Open output channel
.const KERNAL_CHKIN = $FFC6 // Open input channel
.const KERNAL_CLRCHN = $FFCC // Clear I/O channels
.const KERNAL_LOAD = $FFD5 // Load file into memory
// VIC-II registers.
.const VIC_BORDER_COLOR = $D020 // Border color register
.const VIC_BACKGROUND_COLOR = $D021 // Background color register
// System addresses.
.const CURRENT_TEXT_COLOR = $0286 // Current cursor color
.const KEYBOARD_BUFFER_COUNT = $C6 // Number of characters in keyboard buffer
.const CURSOR_BLINK_ENABLE = $CC // Cursor blink: 0 = enabled, non-zero = disabled
.const CURSOR_CHAR_UNDER = $CE // Character stored under cursor (screen code)
.const CURSOR_BLINK_PHASE = $CF // Cursor phase: 0 = not displayed, non-zero = displayed
.const CURSOR_LINE_POINTER = $D1 // Pointer to current screen line (lo/hi)
.const CURSOR_COLUMN = $D3 // Current cursor column position
.const BRK_VECTOR = $0316 // BRK interrupt vector (low/high)
.const BASIC_WARM_START = $A002 // BASIC ROM warm start vector (indirect)
// Zero page pointers.
.const ZP_PTR_1 = $FB // General 16-bit pointer
.const ZP_PTR_2 = $FD // Secondary 16-bit pointer
.const ZP_SCRATCH = $02 // Temporary scratch byte
.const ZP_PR_INDEX = $22 // print_registers loop index (avoids
// conflict with ZP_SCRATCH used by
// print_hex_word)
.const ZP_START_ADDR = $23 // L command: file start address (lo/hi,
// 2 bytes: $23/$24)
// Color codes.
.const COLOR_BLACK = $00 // Black
.const COLOR_GREEN = $05 // Green
// Character codes.
.const CLEAR_SCREEN = $93 // Clear screen control code
.const CARRIAGE_RETURN = $0D // Carriage return
.const SPACE = $20 // Space character
.const CURSOR_LEFT = $9D // Cursor left
.const CURSOR_RIGHT = $1D // Cursor right
.const DELETE = $14 // DEL key (INST/DEL without SHIFT)
.const COMMA = $2C // Comma character
.const DOUBLE_QUOTE = $22 // Double quote character
// Buffer sizes.
.const INPUT_BUFFER_SIZE = 40 // Input buffer size in bytes
// Tokenizer limits.
.const MAX_TOKENS = 12 // Maximum tokens per command line
// Display formatting.
.const ECHO_INDENT = 2 // Spaces before output
// Data entry limits.
.const A_MAX_BYTES_PER_LINE = 10 // Max bytes per A command line
.const A_MAX_NIBBLES_PER_LINE = 20 // Max nibbles per line (10 * 2)
//==============================================================================
// Program Entry Point
//==============================================================================
*= $C000 "Code Probe"
entry:
// Set border and background colors to black.
lda #COLOR_BLACK
sta VIC_BORDER_COLOR
sta VIC_BACKGROUND_COLOR
// Set text color to green.
lda #COLOR_GREEN
sta CURRENT_TEXT_COLOR
// Clear the screen.
lda #CLEAR_SCREEN
jsr KERNAL_CHROUT
// Print title banner.
lda #<title_string
ldx #>title_string
jsr print_string
jsr print_blank_line
// Initialize shadow registers to default values.
// A, X, Y, SP, PC default to zero. P defaults to $20
// (unused bit 5 set). IO reads the current processor
// port value from address $01.
lda #$00
sta shadow_a
sta shadow_x
sta shadow_y
sta shadow_sp
sta shadow_pc
sta shadow_pc + 1
lda #$20
sta shadow_p
lda $01
sta shadow_io
// Save original BRK vector and install Code Probe's
// BRK handler for G command return.
lda BRK_VECTOR
sta original_brk_vector
lda BRK_VECTOR + 1
sta original_brk_vector + 1
sei
lda #<brk_handler
sta BRK_VECTOR
lda #>brk_handler
sta BRK_VECTOR + 1
cli
// Fall through to the monitor prompt loop.
//==============================================================================
// Monitor Prompt Loop
//==============================================================================
main_loop:
jsr print_prompt
jsr read_line
jsr print_newline
jsr tokenize
jsr dispatch_command
jmp main_loop
//==============================================================================
// Command Handlers
//==============================================================================
//------------------------------------------------------------------------------
//
// Subroutine: cmd_d
//
// Description:
//
// Hex dump command. Displays memory contents from start_address to
// end_address (inclusive) in rows of 8 bytes, with hex values and
// character glyphs.
//
// Syntax: D <start_address> <end_address>
//
//------------------------------------------------------------------------------
cmd_d:
// Validate argument count.
lda token_count
cmp #$03
bcs cmd_d_parse_start
jmp cmd_s_error
cmd_d_parse_start:
// Parse start address (token 1).
ldx #$01
jsr get_token_address
jsr parse_hex_word
bcc cmd_d_error
lda hex_parse_result
sta d_current_address
lda hex_parse_result + 1
sta d_current_address + 1
// Parse end address (token 2).
ldx #$02
jsr get_token_address
jsr parse_hex_word
bcc cmd_d_error
lda hex_parse_result
sta d_end_address
lda hex_parse_result + 1
sta d_end_address + 1
// Calculate byte count: end - start + 1.
lda d_end_address
sta ZP_PTR_1
lda d_end_address + 1
sta ZP_PTR_1 + 1
lda d_current_address
ldx d_current_address + 1
jsr subtract_16
lda #$01
ldx #$00
jsr add_16
lda ZP_PTR_1
sta d_byte_count
lda ZP_PTR_1 + 1
sta d_byte_count + 1
// Row loop.
cmd_d_row_loop:
jsr print_dump_row
bcs cmd_d_summary // Carry set = address wrapped past $FFFF
// Check if d_current_address > d_end_address.
lda d_current_address
sta ZP_PTR_1
lda d_current_address + 1
sta ZP_PTR_1 + 1
lda d_end_address
sta ZP_PTR_2
lda d_end_address + 1
sta ZP_PTR_2 + 1
jsr compare_16
bcc cmd_d_row_loop // current < end: more rows
beq cmd_d_row_loop // current == end: more rows
cmd_d_summary:
lda d_byte_count
sta ZP_PTR_1
lda d_byte_count + 1
sta ZP_PTR_1 + 1
jmp print_byte_count_summary
cmd_d_error:
jmp cmd_s_error
//------------------------------------------------------------------------------
//
// Subroutine: cmd_a
//
// Description:
//
// Alter mode command. Enters alter mode for writing hex bytes
// directly to RAM starting at the specified address.
//
// - The user types hex digits one at a time via GETIN.
//
// - After each byte (two nibbles), the cursor auto-advances past the space
// separator.
//
// - Return commits the current line.
//
// - Return on an empty line exits.
//
// - A full line of 10 bytes auto-commits.
//
// Syntax: A <address>
//
//------------------------------------------------------------------------------
cmd_a:
// Validate argument count.
lda token_count
cmp #$02
bcs cmd_a_parse
jmp cmd_s_error
cmd_a_parse:
// Parse start address (token 1).
ldx #$01
jsr get_token_address
jsr parse_hex_word
bcc cmd_a_error
// Initialize state.
lda hex_parse_result
sta a_line_address
lda hex_parse_result + 1
sta a_line_address + 1
lda #$00
sta a_nibble_count
sta a_cursor_pos
sta a_total_bytes
sta a_total_bytes + 1
// Clear keyboard buffer to prevent leftover characters from the
// monitor prompt input being processed as alter mode data.
lda #$00
sta KEYBOARD_BUFFER_COUNT
// Print first line prompt.
jsr a_print_line_prompt
// Key input loop. CHROUT disables the cursor blink flag ($CC)
// during character output. Re-enable it each time we return to
// the polling loop so the cursor is visible while waiting for
// the next keypress.
cmd_a_enable_cursor:
lda #$00
sta CURSOR_BLINK_ENABLE
cmd_a_key_loop:
jsr KERNAL_GETIN
beq cmd_a_key_loop // No key pressed
// Dispatch key.
cmp #CARRIAGE_RETURN
bne cmd_a_not_return
// Return pressed — remove cursor from screen, then commit or exit.
jsr a_cursor_off
lda a_nibble_count
beq cmd_a_exit
jsr a_commit_line
jsr print_newline
jsr a_print_line_prompt
jmp cmd_a_enable_cursor
cmd_a_not_return:
cmp #CURSOR_LEFT
beq cmd_a_do_left
cmp #CURSOR_RIGHT
beq cmd_a_do_right
cmp #DELETE
beq cmd_a_do_delete
// Check if hex digit.
jsr is_hex_digit
bcc cmd_a_enable_cursor // Not hex, re-enable cursor and poll
// Handle hex digit entry.
jsr a_handle_hex_digit
jmp cmd_a_enable_cursor
cmd_a_do_left:
jsr a_handle_cursor_left
jmp cmd_a_enable_cursor
cmd_a_do_right:
jsr a_handle_cursor_right
jmp cmd_a_enable_cursor
cmd_a_do_delete:
jsr a_handle_delete
jmp cmd_a_enable_cursor
cmd_a_exit:
// Exit alter mode with byte count summary.
jsr print_newline
lda a_total_bytes
sta ZP_PTR_1
lda a_total_bytes + 1
sta ZP_PTR_1 + 1
jmp print_byte_count_summary
cmd_a_error:
jmp cmd_s_error
//------------------------------------------------------------------------------
//
// Subroutine: cmd_r
//
// Description:
//
// Register display and set command.
//
// R — Display all shadow register values.
// R <r> <v> — Set register <r> to value <v> and display.
//
// 8-bit registers (A, X, Y, SP, P, IO) require exactly 2 hex digits.
// The 16-bit register (PC) requires exactly 4 hex digits.
//
//------------------------------------------------------------------------------
cmd_r:
// Dispatch: R alone → display, R <reg> <val> → set.
lda token_count
cmp #$01
beq cmd_r_display
cmp #$03
bcs cmd_r_set
jmp cmd_r_error
cmd_r_display:
jmp print_registers
cmd_r_set:
// Look up register name (token 1) in the register table.
ldx #$01
jsr get_token_address
cmd_r_find_loop:
lda register_name_table, y
sta ZP_PTR_2
lda register_name_table + 1, y
sta ZP_PTR_2 + 1
// Check for end sentinel ($0000).
ora ZP_PTR_2
beq cmd_r_error
sty r_table_index
jsr compare_string
beq cmd_r_matched
// Advance to next entry (4 bytes).
ldy r_table_index
iny
iny
iny
iny
jmp cmd_r_find_loop
cmd_r_matched:
// Retrieve shadow offset and expected width from table.
ldy r_table_index
lda register_name_table + 2, y
sta r_shadow_offset
lda register_name_table + 3, y
// Get value token (token 2).
pha
ldx #$02
jsr get_token_address
pla
// Dispatch by width: 0 = byte, 1 = word.
bne cmd_r_parse_word
// Parse 8-bit value (2 hex digits).
jsr parse_hex_byte
bcc cmd_r_error
// Validate exact length: next character must be null.
pha
lda ( ZP_PTR_1 ), y
bne cmd_r_error_pop
pla
// Store value in shadow register.
ldx r_shadow_offset
sta shadow_registers, x
jmp cmd_r_show
cmd_r_parse_word:
// Parse 16-bit value (4 hex digits).
jsr parse_hex_word
bcc cmd_r_error
// Validate exact length.
lda ( ZP_PTR_1 ), y
bne cmd_r_error
// Store value in shadow register (low byte, then high byte).
ldx r_shadow_offset
lda hex_parse_result
sta shadow_registers, x
lda hex_parse_result + 1
sta shadow_registers + 1, x
jmp cmd_r_show
cmd_r_show:
jmp print_registers
cmd_r_error_pop:
pla
cmd_r_error:
jmp cmd_s_error
//------------------------------------------------------------------------------
//
// Subroutine: cmd_rf
//
// Description:
//
// Register and flag display/set command.
//
// RF — Display registers and expanded flag bit-view.
// RF <flag> <b> — Set flag <flag> to bit value <b> (0 or 1) and display.
//
// Flags: N (bit 7), V (6), - (5), B (4), D (3), I (2), Z (1), C (0).
//
//------------------------------------------------------------------------------
cmd_rf:
// Dispatch: RF alone → display, RF <flag> <bit> → set.
lda token_count
cmp #$01
beq cmd_rf_display
cmp #$03
bcs cmd_rf_set
jmp cmd_rf_error
cmd_rf_display:
jmp cmd_rf_show
cmd_rf_set:
// Look up flag name (token 1) in the flag name table.
ldx #$01
jsr get_token_address
cmd_rf_find_loop:
lda flag_name_table, y
sta ZP_PTR_2
lda flag_name_table + 1, y
sta ZP_PTR_2 + 1
// Check for end sentinel.
ora ZP_PTR_2
beq cmd_rf_error
sty rf_table_index
jsr compare_string
beq cmd_rf_matched
// Advance to next entry (2 bytes).
ldy rf_table_index
iny
iny
jmp cmd_rf_find_loop
cmd_rf_matched:
// Convert byte offset to flag index and get bit mask.
lda rf_table_index
lsr
tax
lda flag_bit_table, x
sta rf_bit_mask
// Get bit value token (token 2).
ldx #$02
jsr get_token_address
// Validate: exactly 1 character, must be '0' or '1'.
lda ( ZP_PTR_1 ), y
sta ZP_SCRATCH
iny
lda ( ZP_PTR_1 ), y
bne cmd_rf_error
lda ZP_SCRATCH
cmp #$30 // '0'
beq cmd_rf_clear_bit
cmp #$31 // '1'
beq cmd_rf_set_bit
jmp cmd_rf_error
cmd_rf_clear_bit:
lda rf_bit_mask
eor #$FF
and shadow_p
sta shadow_p
jmp cmd_rf_show
cmd_rf_set_bit:
lda rf_bit_mask
ora shadow_p
sta shadow_p
cmd_rf_show:
jsr print_registers
jmp print_flags
cmd_rf_error:
jmp cmd_s_error
//------------------------------------------------------------------------------
//
// Subroutine: cmd_f
//
// Description:
//
// Fill memory command. Fills a range of memory with a specified byte
// value. If the fill range would exceed $FFFF, the operation is
// truncated to the boundary and an overflow error is printed.
//
// Syntax: F <address> <byte_count> <byte_value>
//
//------------------------------------------------------------------------------
cmd_f:
// Validate argument count (need 4 tokens: F, address, count, value).
lda token_count
cmp #$04
bcs cmd_f_parse_address
jmp cmd_s_error
cmd_f_parse_address:
// Parse address (token 1).
ldx #$01
jsr get_token_address
jsr parse_hex_word
bcc cmd_f_error
lda hex_parse_result
sta f_address
lda hex_parse_result + 1
sta f_address + 1
// Parse byte count (token 2).
ldx #$02
jsr get_token_address
jsr parse_hex_word
bcc cmd_f_error
lda hex_parse_result
sta f_byte_count
lda hex_parse_result + 1
sta f_byte_count + 1
// Parse byte value (token 3).
ldx #$03
jsr get_token_address
jsr parse_hex_byte
bcc cmd_f_error
sta f_byte_value
lda ( ZP_PTR_1 ), y
bne cmd_f_error
// If byte count is zero, nothing to fill.
lda f_byte_count
ora f_byte_count + 1
bne cmd_f_check_overflow
rts
cmd_f_check_overflow:
// Check for RAM overflow.
// Compute end_address = address + (byte_count - 1).
// If the addition carries past $FFFF, the range overflows.
lda f_address
sta ZP_PTR_1
lda f_address + 1
sta ZP_PTR_1 + 1
lda f_byte_count
sec
sbc #$01
pha
lda f_byte_count + 1
sbc #$00
tax
pla // A = low, X = high of (count - 1)
jsr add_16
bcs cmd_f_overflow
// No overflow.
lda #$00
sta f_overflow_flag
jmp cmd_f_fill
cmd_f_error:
jmp cmd_s_error
cmd_f_overflow:
// Overflow detected. Clamp byte_count so fill reaches $FFFF.
// Adjusted count = $10000 - address = $0000 - address (16-bit).
lda #$00
sec
sbc f_address
sta f_byte_count
lda #$00
sbc f_address + 1
sta f_byte_count + 1
lda #$01
sta f_overflow_flag
cmd_f_fill:
// Save the effective byte count for the summary. The fill loop
// decrements f_byte_count to zero, so preserve it here.
lda f_byte_count
sta f_filled_count
lda f_byte_count + 1
sta f_filled_count + 1
// Set up pointer.
lda f_address
sta ZP_PTR_1
lda f_address + 1
sta ZP_PTR_1 + 1
ldy #$00
cmd_f_fill_loop:
lda f_byte_value
sta ( ZP_PTR_1 ), y
// Decrement byte_count (16-bit).
lda f_byte_count
bne cmd_f_dec_low
dec f_byte_count + 1
cmd_f_dec_low:
dec f_byte_count
// Check if byte_count reached zero.
lda f_byte_count
ora f_byte_count + 1
beq cmd_f_fill_done
// Increment pointer (16-bit).
inc ZP_PTR_1
bne cmd_f_fill_loop
inc ZP_PTR_1 + 1
jmp cmd_f_fill_loop
cmd_f_fill_done:
// Print byte count summary.
lda f_filled_count
sta ZP_PTR_1
lda f_filled_count + 1
sta ZP_PTR_1 + 1
jsr print_byte_count_summary
// If overflow was detected, print error.
lda f_overflow_flag
beq cmd_f_done
lda #<error_ram_overflow
ldx #>error_ram_overflow
jmp print_error
cmd_f_done:
rts
//------------------------------------------------------------------------------
//
// Subroutine: cmd_t
//
// Description: