Hello again everyone. I've been wanting to make a game for the X16 lately, but for what I want to do I needed to figure out a good way to stream data off a file from any random position. You can't do that yet within the r38 emulator (as far as I know). You would have to split up the file into smaller ones. However, you can do this with the POSITION command in CMDR-DOS. This does mean you must have an SD card for this to work.
The syntax for the POSITION command is:
Quote
P <channel id> <low byte> <middle-low byte> <middle-high byte> <high byte>
You have to specify both the channel the file is on and the 32-bit position you want to use. Those 5 arguments must be actual byte values, and not ascii representations of the values!
In BASIC, it is as simple as:
Quote
10 OPEN 1,8,2,"TEST,P,R" REM Open the file "TEST" from the SD card on channel 2
20 DOS"P"+CHR$(2)+CHR$($80)+CHR$(0)+CHR$(0)+CHR$(0) REM Set the position of the file open on channel 2 to be offset $00000080
30 FOR X=1 TO 10: GET#1,A$: PRINT A$: NEXT X REM Read and print out the 10 characters from that position
40 CLOSE 1 REM Close the file
REM Note: I don't know a lot about BASIC. If there is a better way to print out the characters then let me know!
Assembly is more verbose, but it is basically the same idea. You first open the file, set the file offset, read bytes into memory, then close the file.
Because there is a lot more code to go through I have included an attachment with the source code of the example. I personally use the ACME assembler, but it shouldn't be difficult to adapt to other assemblers. You should be able to get a program file when you assemble main.asm. Once you have the .prg file built, place it and the TEST file in an SD card image and run it in the emulator.
The TEST file includes a bunch of ascii text. I'm interested in retrieving the "HELLO X16!" string without going through any of the padding.
The first thing we need to do is open the file.
(All of the following code will be in the attachment, so you don't need to copy & paste)
; fs_open
; Opens a file to be read.
; Arguments:
; r0 = Pointer to a null-terminated string that is the filepath to open.
; r1L = Channel to open.
; Assumptions:
; * SD Card is being used and is set to device #8
; * String in r0 is less than 256 characters.
; * Channel is not 0, 1, or 15.
fs_open:
!zone {
+set_ram_bank RAM_BANK_KERNAL_DOS ; Make sure the ram bank is set to 0.
lda (r0) ; Load the first character in the string
bne .setup ; Checking for the null-terminator
rts ; End early if the input is an empty string.
.setup:
ldx r1L ; Open passed in channel
jsr CHKIN
lda #8 ; SD Card in device #8
jsr LISTEN
lda r1L
ora #$F0 ; $FX = Open file in channel X (r1L)
jsr SECOND
.loop_init:
ldy #0 ; Start at the first character.
.send_char:
lda (r0),Y ; Read the current character
beq .cleanup_and_exit ; Exit if the byte is the null terminator
jsr IECOUT ; Send character to the serial bus
iny ; Move to the next character
bra .send_char ; Keep looping
.cleanup_and_exit:
jmp UNLSN ; Stop listening and send the filename.
}
This function was adapted from my
execute_dos_command function that I shared in my original post, since opening up channel 15 is how you execute dos commands in the first place. Do note that the high-ram bank must be set to zero for this code to work.
Speaking of which, we are going to use a modified execute_dos_command function to run the POSITION dos command, but first we have to setup that command in RAM:
; The channel 15 command that sets the position of a file.
DOS_FILE_POSITION: !scr "P"
DOS_FILE_POSITION_CHANNEL: !byte 0
DOS_FILE_POSITION_OFFSET: !byte 0, 0, 0, 0
DOS_FILE_POSITION is used as the string pointer for the command. DOS_FILE_POSITION_CHANNEL points to the channel id, and DOS_FILE_POSITION_OFFSET points to the 32-bit offset. In the attachment I created a bunch of macros to help me simplify the code. I'm not going to go through them since it would take too long, so here is an equivalent to the line 20 of the basic code:
lda #2 ; Using channel 2
sta DOS_FILE_POSITION_CHANNEL ; Set the channel of the file to 2
lda #$80 ; "HELLO X16!" is located at offset $80 of the TEST file
sta DOS_FILE_POSITION_OFFSET ; Store $80 to the low byte
; I technically don't need these 3, since they are never modified.
; stz DOS_FILE_POSITION_OFFSET + 1 ; Store 0 to the middle-low byte
; stz DOS_FILE_POSITION_OFFSET + 2 ; Store 0 to the middle-high byte
; stz DOS_FILE_POSITION_OFFSET + 3 ; Store 0 to the high byte
lda #<DOS_FILE_POSITION ; Load the DOS_FILE_POSITION pointer into r0
sta r0L
lda #>DOS_FILE_POSITION
sta r0H
lda #6
sta r1L ; Length of the POSITION command (6 bytes)
jsr execute_dos_command_n ; Execute the command to set the file offset to $80
Now that we have the position ready, we now need to read the bytes from the file. To do this we need to set the ROM bank to 2, since that bank contains the function to read bytes. That function is called
file_read_block with the arguments: .A is the number of bytes to read and .Y/.X is the destination address to write to. To help with this I wrote some macros to save the ROM bank & call the file_read_block function:
; Read bytes from a file into RAM.
!macro m_fs_read_to_address .dest, .length {
ldx #<.dest
ldy #>.dest
lda #.length
jsr file_read_block ; ROM bank must be set to 2 before this is called!
}
; Saves the current ROM bank and sets it to the CBDOS bank.
!macro m_fs_start_reading {
+push_current_rom_bank_onto_stack
+set_rom_bank ROM_BANK_CBDOS ; ROM_BANK_CBDOS = 2
}
; Restores the previous ROM bank.
!macro m_fs_stop_reading {
+pull_current_rom_bank_from_stack
}
You can find the macros: set_rom_bank, push_current_rom_bank_onto_stack, and pull_current_rom_bank_from_stack in the
banks.inc file in the attachments source code.
After the bytes are read, the file finally needs to be closed so that the channel can be used for other files. To do that you simple call the
fs_close function.
; fs_close
; Closes a channel so that it can be used again.
; Arguments:
; r0L = Channel to close.
; Assumptions:
; * SD Card is being used and is set to device #8
; * String in r0 is less than 256 characters.
; * Channel is not 0, 1, or 15.
fs_close:
!zone {
ldx r0L ; Open passed in channel
jsr CHKIN
lda #8 ; SD Card in device #8
jsr LISTEN
lda r0L
ora #$E0 ; $EX = Close file in channel X (r0L)
jsr SECOND
jmp CLRCHN ; Close the channel and return
}
When you look at the main.asm file, you'll see these macros being used:
FILENAME: !SCR "TEST", 0
CHANNEL_FILE = 2 ; Channel to use. Can be any value within [2, 14]
SRC_OFFSET = $80 ; Position within the file
DEST_ADDRESS = $20 ; RAM address to write to
READ_LENGTH = 10 ; Number of bytes to copy
main:
+m_fs_open CHANNEL_FILE, FILENAME ; Open the file
+m_fs_set_file_position CHANNEL_FILE, SRC_OFFSET ; Set the current position of the file.
+m_fs_start_reading ; Saves the current ROM bank, and sets it to the CBDOS code bank.
+m_fs_read_to_address DEST_ADDRESS, READ_LENGTH ; Read data from the file into RAM.
+m_fs_stop_reading ; Restores the original ROM bank.
+m_fs_close CHANNEL_FILE ; The channel must be closed before using it again.
exit:
rts ; Return to basic.
I'm hoping that those macros make it easy to understand what is happening conceptually. You can look at
fs.asm for more information on what they are actually doing. The indenting isn't required, I just think it looks nice (like python).
When you run the code in the emulator you'll see that nothing happens on the basic screen. When you dump the RAM, you should see that the "HELLO X16!" message appeared at offset $20.
If you have any question or comments, then please leave a reply and I'll try to get back to you as soon I can.
filepos-example-v1.1.zip