-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathshell.asm
More file actions
2955 lines (2435 loc) · 75.7 KB
/
Copy pathshell.asm
File metadata and controls
2955 lines (2435 loc) · 75.7 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
; shell.asm: command shell for loading commands off of a BBFS disk.
#include <dcdos_api.inc.asm>
; BBOS functions
;Set Cursor Pos 0x1001 X, Y None 1.0
;Get Cursor Pos 0x1002 OUT X, OUT Y Y, X 1.0
;Write Char 0x1003 Char, MoveCursor None 1.0
;Write String 0x1004 StringZ, NewLine None 1.0
;Scroll Screen 0x1005 Num lines to scroll None 1.0
;Get Screen Size 0x1006 OUT Width, OUT Height Height, Width 1.0
define SET_CURSOR_POS 0x1001
define GET_CURSOR_POS 0x1002
define WRITE_CHAR 0x1003
define WRITE_STRING 0x1004
define SCROLL_SCREEN 0x1005
define GET_SCREEN_SIZE 0x1006
;Read Character 0x3001 Blocking Char 1.0
define READ_CHARACTER 0x3001
; Get Drive Count 0x2000 OUT Drive Count Drive Count 1.0
; Check Drive Status 0x2001 DriveNum StatusCode 1.0
define GET_DRIVE_COUNT 0x2000
define CHECK_DRIVE_STATUS 0x2001
; Get BBOS info
define GET_BBOS_INFO 0x0000
; And the offsets to varoious fields
define BBOS_INFO_VERSION 0
define BBOS_INFO_START_ADDR 1
define BBOS_INFO_END_ADDR 2
define BBOS_INFO_INT_HANDLER 3
define BBOS_INFO_API_HANDLER 4
; Drive status codes
define STATE_NO_MEDIA 0
define STATE_READY 1
define STATE_READY_WP 2
define STATE_BUSY 3
; Key codes
define KEY_BACKSPACE 0x10
define KEY_RETURN 0x11
define KEY_ARROW_LEFT 0x82
define KEY_ARROW_RIGHT 0x83
define ASCII_MIN 0x20 ; Space character
define ASCII_MAX 0x7f ; ASCII del character, not used itself.
; How long can a shell command line be? This includes the trailing null.
define SHELL_COMMAND_LINE_LENGTH 128
; How long can a command binary name be, including a trailing null? Max file
; name length minus extension.
define SHELL_COMMAND_LENGTH 13
; What's the BBOS bootloader magic number?
define BBOS_BOOTLOADER_MAGIC 0x55AA
define BBOS_BOOTLOADER_MAGIC_POSITION 511
; We want to load the shell code into high memory, so that if we want to load
; another binary off of a disk, we can fit one of a decent size before it starts
; to overwrite the routines trying to load it.
; BBOS likes to load at 0xF000. If we could relocate ourselves, we could just
; ask it where to load. Unfortunately, we can't, so we just load at a fixed
; offset.
; This leaves us 12k for code above, and 40k for user/loaded code below.
define SHELL_CODE_START 0xA000
; This leaves us a bit under 8k for data and BBOS's VRAM.
define SHELL_DATA_START 0xD000
; TODO: develop a bank switching peripheral to swap out 8k banks or something.
zero:
; Here we have some simple code to move the rest up to high memory
SET I, moveable_start ; This is where we find the code to move
SET J, start ; This is where it goes
; Calculate (at assembly time) how many words to copy
SET C, bootloader_code+BBFS_MAX_SECTOR_SIZE-start
.copy_loop:
; Copy all the words up into higher memory
STI [J], [I]
SUB C, 1
IFN C, 0
SET PC, .copy_loop
; Jump to the code we just moved into place
SET PC, start
moveable_start: ; This is where we read our real code from
.org SHELL_CODE_START
start:
; Now we're running in place.
; Sanity check: make sure the end of our code copied. We expect a set PC, nextword here
; If not, jump there and explode
IFN [halt], 0x7f81
SET PC, halt
; Save the code that just moved us, in case we need it for format.
SET I, zero
SET J, format_copyloader
SET C, moveable_start-zero
.copy_loop:
STI [J], [I]
SUB C, 1
IFN C, 0
SET PC, .copy_loop
; The drive we loaded off of is in A.
SET [drive], A
; Configure our interrupt handler. See
; <https://github.com/MadMockers/BareBonesOS>
SET A, GET_BBOS_INFO ; Get BBOS Info
SUB SP, 1 ; placeholder for return value
INT BBOS_IRQ_MAGIC ; invoke BBOS
SET A, POP ; pop address of info struct into A
; update global bbos_int_addr with the value from the struct
SET [bbos_int_addr], [A+BBOS_INT_HANDLER]
IAS dcdos_interrupt_handler ; Set system interrupt handler
; Print the intro
SET PUSH, str_ready ; Arg 1: string to print
SET PUSH, 1 ; Arg 2: whether to print a newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
command_loop:
; Print the prompt
SET PUSH, [drive] ; Arg 1: drive number
JSR shell_print_prompt
ADD SP, 1
; Read a command
SET PUSH, command_buffer
SET PUSH, SHELL_COMMAND_LINE_LENGTH
JSR shell_readline
ADD SP, 2
; Try and execute it
SET PUSH, command_buffer
SET PUSH, [drive]
JSR shell_exec
ADD SP, 2
; Read more commands
SET PC, command_loop
; Functions
; shell_readline(*buffer, length)
; Read a line into the buffer. Allow arrow keys to move left/right for insert,
; and backspace for backspace. Returns when enter is pressed.
; shell_print_prompt(drive_number)
; Print the prompt, noting the given drive as a letter, starting from A.
; shell_strncmp(*string1, *string2, ignore_case, length)
; Compare two strings for equality. Returns 1 if they are equal, 0 otherwise.
; If ignore_case is 1, then case is ignored.
; shell_exec(*command, drive_number)
; Try executing the command in the given buffer, by first going through
; builtins and then out to the given disk for files with executable extension.
; shell_open(*file, *filename, create)
; Populate a file object by opening the given file object on the appropriate
; drive (either the current one or one derived from a leading A:\ in the
; filename). Clobbers the global directory and dirinfo space, and may
; initialize drive2, device2, and volume2 if needed. If create is specified,
; creates the file if it can't be found. Returns an error code, and the file
; index in the global directory if found.
; shell_resolve_drive(character)
; Turn a drive letter into a drive number, or 0xFFFF if a bad drive letter.
; shell_atoi(*string)
; Return the decimal number represented by the given string, or 0 if the
; string is empty or unparseable.
; Builtin functions (shell commands)
;
; All of these take one argument: the argument buffer (which has the remainder
; of the typed command after the command name and any whitespace). They may
; access the global [drive], and probably use all the file and directory globals
; for their own purposes.
;
; shell_builtin_ver(*arguments)
; Print the shell version information.
;
; shell_builtin_format(*arguments)
; Format the disk in the drive with the given letter as bootable.
;
; shell_builtin_dir(*arguments)
; List the files in the root directory on the current or specified drive
;
; shell_builtin_copy(*arguments)
; Copies the file named with the first argument to the file named with the
; second argument. Filenames may be prefixed as <DRIVE>:\.
;
; shell_builtin_del(*arguments)
; Delete file, with drive letter support as in COPY above.
;
; shell_builtin_load(*arguments)
; Load a file at address 0 and execute it. Loaded code can try returning with
; a SET PC, POP if it didn't clobber our memory, in which case this function
; returns 1. If the file could not be loaded, this function returns 0.
;
; shell_builtin_image(*arguments)
; Images the disk in the drive given as the first argument to the file named
; with the second argument. Filenames may be prefixed as <DRIVE>:\. Saves
; complete sectors of the disk, until a sector of all 0s is encountered.
; Can take an optional sector count argument instead.
; shell_readline(*buffer, length)
; Read a line into a buffer.
; [Z+1]: buffer address
; [Z]: Buffer size
shell_readline:
; Set up frame pointer
SET PUSH, Z
SET Z, SP
ADD Z, 2
SET PUSH, A ; BBOS calls
SET PUSH, B ; Cursor position in the buffer
SET PUSH, C ; Character we're reading
SET PUSH, X ; Cursor horizontal scratch
SET PUSH, Y ; Cursor vertical scratch
; Set up a cursor position in the buffer
SET B, [Z+1]
; Shrink our buffer length in [Z] to just the number of printable characters
; we can hold, leaving 1 word for the trailing null.
SUB [Z], 1
; Get the cursor position on the screen, and keep it on the stack.
SUB SP, 2 ; Args 1 and 2: X and Y (output)
SET A, GET_CURSOR_POS
INT BBOS_IRQ_MAGIC
.key_loop:
; Wait for a key press, blocking
SET PUSH, 1 ; Arg 1: whether to block
SET A, READ_CHARACTER
INT BBOS_IRQ_MAGIC
SET C, POP ; Returns the key code.
IFE C, KEY_RETURN
; If it's enter, print a newline and return
SET PC, .key_return
IFE C, KEY_BACKSPACE
; If it's backspace, delete a character.
SET PC, .key_backspace
IFE C, KEY_ARROW_LEFT
; If it's a left arrow, move the cursor left
SET PC, .key_arrow_left
IFL C, ASCII_MIN
; Other non-printable. Try again
SET PC, .key_loop
IFL ASCII_MAX, C
; Other non-printable. Try again
SET PC, .key_loop
; Otherwise it's printable
SET PC, .key_printable
.key_return:
; The user pressed enter.
; Add a null at the end of the buffer
SET [B], 0
; Print a newline
SET PUSH, str_newline
SET PUSH, 1 ; With newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
; Return
SET PC, .return
.key_backspace:
; The user pressed backspace.
IFE B, [Z+1]
; We're already at the start of the buffer. Nothing to delete.
SET PC, .key_loop
; Move the cursor in the buffer
SUB B, 1
; Add a null so we can print the buffer
SET [B], 0
; Move the cursor on the screen back to the start of the string we're
; typing. TODO: incrementally shift left and un-word-wrap.
; Args are already on the stack from the GET_CURSOR_POS call
SET A, SET_CURSOR_POS
INT BBOS_IRQ_MAGIC
; Now print the now-shorter string
SET PUSH, [Z+1] ; Arg 1: string to print
SET PUSH, 0 ; Arg 2: whether to print a newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
; Print a space in place
SET PUSH, 0x20 ; Arg 1: character
SET PUSH, 0 ; Arg 2: move cursor
SET A, WRITE_CHAR
INT BBOS_IRQ_MAGIC
ADD SP, 2
; Read another key
SET PC, .key_loop
.key_arrow_left:
; TODO: don't ignore
SET PC, .key_loop
.key_arrow_right:
; TODO: don't ignore
SET PC, .key_loop
.key_printable:
; If it's a printable character, write it to the buffer and the screen.
; TODO: move the rest of the characters/manage a gap buffer and update the
; screen with everything after the cursor.
SET A, [Z+1]
ADD A, [Z]
SUB A, 1
IFG B, A
; We've filled the usable area of the buffer. Drop this character
SET PC, .key_loop
; Save in the buffer
SET [B], C
ADD B, 1
; If the cursor is on the right edge of the screen, print a newline. This
; ensures that when we hit the lower right corner of the screen, we don't
; just keep overstriking the same character.
; First get the screen size
SUB SP, 2
SET A, GET_SCREEN_SIZE
INT BBOS_IRQ_MAGIC
SET Y, POP ; Save the height
SET X, POP ; Save the width.
; Now get the cursor position.
SUB SP, 2
SET A, GET_CURSOR_POS
INT BBOS_IRQ_MAGIC
SUB Y, POP ; Subtract the cursor row
SUB X, POP ; Subtract the cursor column.
; Write the typed character to the screen
SET PUSH, C ; Arg 1: character to write
SET PUSH, 1 ; Arg 2: move cursor or not
SET A, WRITE_CHAR
INT BBOS_IRQ_MAGIC
ADD SP, 2
IFN X, 1
; We weren't about to fill in the last column, so no scrolling is needed
SET PC, .no_scroll_needed
IFN Y, 1
; We were not on the last row of the terminal, which is really the only
; place we need the newline.
SET PC, .no_scroll_needed
; Else we're out of room on this line. Print a newline.
SET PUSH, str_newline
SET PUSH, 1 ; With newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
; We need to update our home position for when we re-print on
; backspace.
SUB [SP], 1
.no_scroll_needed:
SET PC, .key_loop
.return:
ADD SP, 2 ; Delete the cursor start X and Y
SET Y, POP
SET X, POP
SET C, POP
SET B, POP
SET A, POP
SET Z, POP
SET PC, POP
; shell_print_prompt(drive_number)
; Print the prompt, including drive letter.
; [Z]: drive number
shell_print_prompt:
; Set up frame pointer
SET PUSH, Z
SET Z, SP
ADD Z, 2
SET PUSH, A ; BBOS calls
; Print the drive letter as a character
SET PUSH, [Z] ; Arg 1: Character to print
ADD [SP], 0x41 ; Add to 'A'
SET PUSH, 1 ; Arg 2: move cursor
SET A, WRITE_CHAR
INT BBOS_IRQ_MAGIC
ADD SP, 2
; Print the rest of the prompt
SET PUSH, str_prompt ; Arg 1: string to print
SET PUSH, 0 ; Arg 2: whether to print a newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
; Return
SET A, POP
SET Z, POP
SET PC, POP
; shell_strncmp(*string1, *string2, ignore_case, length)
; Compare two strings for equality
; [Z+3]: string 1 address
; [Z+2]: string 2 address
; [Z+1]: ignore case flag
; [Z]: max length to compare to
; Returns: equality flag in [Z]
shell_strncmp:
; Set up frame pointer
SET PUSH, Z
SET Z, SP
ADD Z, 2
SET PUSH, A ; String 1 character
SET PUSH, B ; String 2 character
SET PUSH, C ; Remaining length
SET PUSH, I ; String 1 char address
SET PUSH, J ; String 2 char address
SET I, [Z+3] ; Point to start of first string
SET J, [Z+2] ; Point to start of second string
SET C, [Z] ; Load up the number of characters to compare
.loop:
IFE C, 0
; Ran out of characters without finding a difference
SET PC, .equal
; Load the next characters from the string
SET A, [I]
SET B, [J]
IFN [Z+1], 1
; They didn't set the ignore case flag, so we shouldn't uppercase
; everything.
SET PC, .no_fix_case
IFG A, 0x60 ; Character is 'a' or greater
IFL A, 0x7B ; And character is 'z' or less
SUB A, 32 ; Move down by the offset from A to a
IFG B, 0x60
IFL B, 0x7B
SUB B, 32
.no_fix_case:
IFN A, B
; We found a difference
SET PC, .unequal
IFE A, 0
; No difference, but they both end here
SET PC, .equal
; Otherwise keep going
; Advanc ein the strings
ADD I, 1
ADD J, 1
; Knock a character off the reamining count
SUB C, 1
SET PC, .loop
.equal:
SET [Z], 1
SET PC, .return
.unequal:
SET [Z], 0
.return:
SET J, POP
SET I, POP
SET C, POP
SET B, POP
SET A, POP
SET Z, POP
SET PC, POP
; shell_exec(*command, drive_number)
; Try the command at the start of the buffer as a builtin, then as a file with
; executable extension on the given disk, then as a file with the executable
; extension appended.
; [Z+1]: command string buffer
; [Z]: drive number to search
shell_exec:
; Set up frame pointer
SET PUSH, Z
SET Z, SP
ADD Z, 2
SET PUSH, A ; BBOS and pointer into builtins table
SET PUSH, B ; Pointer to builtin name
SET PUSH, C ; Pointer to builtin function
SET PUSH, X ; Pointer to where args start in command string
SET PUSH, Y ; Scratch
; First find where in the command string the command ends and the args start
SET X, [Z+1]
.args_loop:
IFE [X], 0
; We hit the trailing null, so the args are empty
SET PC, .args_done
IFE [X], 0x20 ; We found a space character.
SET PC, .scan_spaces
; Otherwise it's more command name
ADD X, 1
SET PC, .args_loop
.scan_spaces:
; We found spaces.
; Replace the first one with a null to terminate the command name string.
SET [X], 0
.space_loop:
; Advance
ADD X, 1
IFE [X], 0x20
; Advance to the first non-space character.
SET PC, .space_loop
.args_done:
; Now [Z+1] is the null-terminated command name, and X points to the null-
; terminated argument string.
; Save the argument string in case we end up calling a program and it wants
; to get it through the interrupt API
SET [arguments_start], X
; Skip empty commands
SET A, [Z+1]
IFE [A], 0
SET PC, .return
; Compare command name against our builtin table.
SET A, builtins_table
.builtins_loop:
SET B, [A] ; Load the command
SET C, [A+1] ; And the function
IFE B, 0
IFE C, 0
; We are out of builtins. This is the null terminator
SET PC, .not_a_builtin
; Call string comparison
SET PUSH, B ; Arg 1: builtin name
SET PUSH, [Z+1] ; Arg 2: typed command
SET PUSH, 1 ; Arg 3: ignore case
SET PUSH, SHELL_COMMAND_LENGTH ; Arg 4: max length
JSR shell_strncmp
SET Y, POP ; Save the equality flag
ADD SP, 3
IFE Y, 1
; This is the right builtin
SET PC, .builtin_found
; Otherwise move on. This is the size of one of these table records. TODO:
; define a proper struct.
ADD A, 2
SET PC, .builtins_loop
.builtin_found:
; We found the right builtin table entry. Call the builtin.
SET PUSH, X ; Arg 1: null-terminated argument string
JSR C ; Call the function address we found in the table.
ADD SP, 1 ; Ignore anything returned
SET PC, .return
.not_a_builtin:
; TODO: see if we said "B:" and if so switch to that drive.
SET Y, [Z+1] ; Start at the start of the string
ADD Y, 1 ; Look at the second character
IFN [Y], 0x3A ; Not a drive if it's not <char>:
SET PC, .not_a_drive
IFE [Y+1], 0x20 ; Catch "A: "
SET PC, .is_a_drive
IFE [Y+1], 0 ; Catch "A:"
SET PC, .is_a_drive
SET PC, .not_a_drive
.is_a_drive:
; They may be asking for a drive
; Grab the letter
SET Y, [Z+1] ; Look at the start of the string again
SET Y, [Y]
SET PUSH, Y
JSR shell_resolve_drive
SET Y, POP
IFE Y, 0xFFFF
; We couldn't resolve the drive letter
SET PC, .error_bad_drive
; If we could, go to that drive
SET [drive], Y
SET PC, .return
.not_a_drive:
; How long is the command?
SET A, [Z+1]
; Does this command contain an extension?
SET B, 0
.command_length_loop:
IFE [A], 0
SET PC, .command_length_done
IFE [A], 0x2E ; We found a dot
SET B, A ; Save its location
ADD A, 1
SET PC, .command_length_loop
.command_length_done:
SUB A, [Z+1] ; Now A is the command length
IFN B, 0
; We found an extension in the string.
SET PC, .try_load_with_extension
; Otherwise, try adding it on and running that. Filenames are 16 chars, so
; we need 12 or fewer for this to work.
IFL A, 13
SET PC, .try_load_without_extension
SET PC, .error_bad_command
; Try loading the command, appending an executable extension
.try_load_without_extension:
; Copy the filename over to our global filename buffer
SET A, [Z+1]
SET B, filename
.buffer_loop:
SET [B], [A]
IFE [A], 0
SET PC, .buffer_loop_done
ADD A, 1
ADD B, 1
SET PC, .buffer_loop
.buffer_loop_done:
; Append the executable extension
SET A, str_executable_extension
.ext_loop:
SET [B], [A]
IFE [A], 0
SET PC, .ext_loop_done
ADD A, 1
ADD B, 1
SET PC, .ext_loop
.ext_loop_done:
; Load it
SET PUSH, filename ; Arg 1: filename string
JSR shell_builtin_load
SET Y, POP
IFE Y, 0
; We didn't load successfully
SET PC, .error_bad_command
; Otherwise it ran
SET PC, .return
.try_load_with_extension:
; A is the filename length and B is the extension start
; See if the extension is executable and if so load from disk.
; Do we have the executable extension?
SET PUSH, str_executable_extension ; Arg 1: string 1
SET PUSH, B ; Arg 2: string 2
SET PUSH, 1 ; Arg 3: ignore case
SET PUSH, 4 ; Arg 4: number of characters (".EXT")
JSR shell_strncmp
SET Y, POP
ADD SP, 3
IFE Y, 0
; Not a loadable binary
SET PC, .error_bad_command
; If we get here, we can just load it with the filename in the command
; buffer.
SET PUSH, [Z+1] ; Arg 1: filename string
JSR shell_builtin_load
SET Y, POP
IFE Y, 0
; We didn't load successfully
SET PC, .error_bad_command
; Otherwise we have finished running the program
SET PC, .return
.error_bad_command:
; Say we couldn't find the command.
; Start with the parsed command name
SET PUSH, [Z+1] ; Arg 1: string to print
SET PUSH, 0 ; Arg 2: newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
; Then say we couldn't find it
SET PUSH, str_not_found ; Arg 1: string to print
SET PUSH, 1 ; Arg 2: newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
SET PC, .return
.error_bad_drive:
; Say we couldn't find the specified drive
SET PUSH, str_error_drive ; Arg 1: string to print
SET PUSH, 1 ; Arg 2: newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
.return:
SET Y, POP
SET X, POP
SET C, POP
SET B, POP
SET A, POP
SET Z, POP
SET PC, POP
; shell_builtin_ver(*arguments)
; Print shell version info.
; [Z]: argument string
shell_builtin_ver:
; Set up frame pointer
SET PUSH, Z
SET Z, SP
ADD Z, 2
SET PUSH, A ; BBOS calls
; Ignore the arguments
; Print the version strings
SET PUSH, str_ver_version1 ; Arg 1: string to print
SET PUSH, 1 ; Arg 2: whether to print a newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
SET PUSH, str_ver_version2 ; Arg 1: string to print
SET PUSH, 1 ; Arg 2: whether to print a newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
; Return
SET A, POP
SET Z, POP
SET PC, POP
; shell_builtin_format(*arguments)
; Format the disk in the drive with the given letter.
; [Z]: argument string, with first letter being drive to format.
shell_builtin_format:
; Set up frame pointer
SET PUSH, Z
SET Z, SP
ADD Z, 2
SET PUSH, A ; BBOS calls
SET PUSH, B ; Drive number
; Try and read a drive number from the argument string
SET B, [Z]
SET B, [B]
; If it's in the lower-case ASCII range, upper-case it
IFL B, 0x7B
IFG B, 0x60
SUB B, 32
IFL B, 0x41
; 'A' is the first valid drive letter
SET PC, .error_bad_drive_letter
IFG B, 0x5A
; 'Z' is the last possible drive letter
SET PC, .error_bad_drive_letter
; Convert from drive letter to drive number.
SUB B, 0x41
; Get the drive count from BBOS
SUB SP, 1
SET A, GET_DRIVE_COUNT
INT BBOS_IRQ_MAGIC
SET A, POP
SUB A, 1 ; We know we have 1 drive, so knock this down to (probably) 7
IFG B, A
; We're out of bounds wrt the drives installed
SET PC, .error_no_drive
; Now check to make sure there's a writable disk
SET PUSH, B
SET A, CHECK_DRIVE_STATUS
INT BBOS_IRQ_MAGIC
SET A, POP
; Shift down to have only the high (status) octet
SHR A, 8
; Check to make sure we're status ready
IFE A, STATE_NO_MEDIA
SET PC, .error_no_media
IFE A, STATE_READY_WP
SET PC, .error_write_protected
IFN A, STATE_READY
SET PC, .error_unknown
; Now we can actually do the work.
; Open the main device on this drive
SET PUSH, device
SET PUSH, B
JSR bbfs_device_open
ADD SP, 2 ; Can't fail
; And open the main volume on that device
SET PUSH, volume
SET PUSH, device
JSR bbfs_volume_open
SET A, POP
ADD SP, 1
IFN A, BBFS_ERR_NONE
IFN A, BBFS_ERR_UNFORMATTED
SET PC, .error_unknown
; Format the volume
SET PUSH, volume
JSR bbfs_volume_format
SET A, POP
IFN A, BBFS_ERR_NONE
SET PC, .error_unknown
; Make a root directory.
; Since it's the first thing on the disk it ends up at the right sector.
SET PUSH, directory ; Arg 1: directory handle to open
SET PUSH, volume ; Arg 2: volume to make the directory on
JSR bbfs_directory_create
SET A, POP
ADD SP, 1
IFN A, BBFS_ERR_NONE
SET PC, .error_unknown
; Save a memory image to "BOOT.IMG" that will boot back to this code.
; Make a file
SET PUSH, file ; Arg 1: file struct to populate
SET PUSH, volume ; Arg 2: volume to work in
JSR bbfs_file_create
SET A, POP
ADD SP, 1
IFN A, BBFS_ERR_NONE
SET PC, .error_unknown
; Stick it in the directory
; Populate an entry for it in the directory
SET [entry+BBFS_DIRENTRY_TYPE], BBFS_TYPE_FILE
SET [entry+BBFS_DIRENTRY_SECTOR], [file+BBFS_FILE_START_SECTOR]
; Pack in a filename
SET PUSH, str_boot_filename ; Arg 1: string to pack
SET PUSH, entry ; Arg 2: place to pack it
ADD [SP], BBFS_DIRENTRY_NAME
JSR bbfs_filename_pack
ADD SP, 2
; Add the entry to the directory (which saves it to disk)
SET PUSH, directory
SET PUSH, entry
JSR bbfs_directory_append
SET A, POP
ADD SP, 1
IFN A, BBFS_ERR_NONE
SET PC, .error_unknown
; Write the copy loader we moved up to high memory, so it can move us back
; to high memory when we reload.
SET PUSH, file ; Arg 1: file pointer to write to
SET PUSH, format_copyloader ; Arg 2: start address
SET PUSH, moveable_start-zero ; Arg 3: length
JSR bbfs_file_write
SET A, POP
ADD SP, 2
IFN A, BBFS_ERR_NONE
SET PC, .error_unknown
; Write the actual program code
SET PUSH, file ; Arg 1: file pointer to write to
SET PUSH, start ; Arg 2: start address
SET PUSH, bootloader_code+BBFS_MAX_SECTOR_SIZE-start ; Arg 3: length
JSR bbfs_file_write
SET A, POP
ADD SP, 2
IFN A, BBFS_ERR_NONE
SET PC, .error_unknown
; And flush. This syncs the device.
SET PUSH, file
JSR bbfs_file_flush
SET A, POP
IFN A, BBFS_ERR_NONE
SET PC, .error_unknown
; Now all we have to do is install the bootloader.
; First set its magic word
SET [bootloader_code+BBOS_BOOTLOADER_MAGIC_POSITION], BBOS_BOOTLOADER_MAGIC
; Then make a raw BBOS call to stick it as the first sector of the drive
SET PUSH, 0 ; Arg 1: sector
SET PUSH, bootloader_code ; Arg 2: pointer
SET PUSH, B ; Arg 3: drive number
SET A, WRITE_DRIVE_SECTOR
INT BBOS_IRQ_MAGIC
ADD SP, 3
; Put back the magic word so we don't change our image
SET [bootloader_code+BBOS_BOOTLOADER_MAGIC_POSITION], 0
; Say we succeeded
SET PUSH, str_format_success ; Arg 1: string to print
SET PUSH, 0 ; Arg 2: whether to print a newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
SET PC, .say_drive_and_return
.error_bad_drive_letter:
; Put the error message
SET PUSH, str_format_usage ; Arg 1: string to print
SET PUSH, 1 ; Arg 2: whether to print a newline
SET A, WRITE_STRING
INT BBOS_IRQ_MAGIC
ADD SP, 2
; Don't try and say the bad drive letter.
set PC, .return
.error_no_drive: