Humanitarian Action via the VMU

Anything related in any way to game development as a whole is welcome here. Tell us about your game, grace us with your project, show us your new YouTube video, etc.

Moderator: PC Supremacists

Post Reply
User avatar
RustIsBetterThanCPP
Chaos Rift Newbie
Chaos Rift Newbie
Posts: 6
Joined: Tue Feb 07, 2023 9:29 pm
Current Project: Terminal Emulator in Rust
Favorite Gaming Platforms: Nintendo Wii
Programming Language of Choice: Rust
Contact:

Humanitarian Action via the VMU

Post by RustIsBetterThanCPP »

Hi all :)

I have a pretty important message to share.

I have been honing my VMU development skills for good. Unfortunately it requires programming in unsafe assembly language, but I used my skills to send a positive message, for the betterment of the software industry. ;)

Here is an image:
rustorbust.png
rustorbust.png (13.06 KiB) Viewed 12125 times
The code for the curious:

Code: Select all

; -----------------------------------------------------------------------------
; Simple VMU Hello World
;
;
; Author:  Shirobon
; Date:    2023/02/11
; -----------------------------------------------------------------------------

    .include "sfr_def.s"        ; Contains definitions of hardware registers
        
; -----------------------------------------------------------------------------
; Interrupt Vectors
; -----------------------------------------------------------------------------

    .org $00        ; Reset Vector
    jmpf __main     ; Jump far past header to entry point
    
    .org $03        ; INT0 Interrupt (External)
    jmp __nop_vec   ; Nothing to do for this interrupt, just return
    
    .org $0B        ; INT1 Interrupt (External)
    jmp __nop_vec   ; Nothing to do for this interrupt, just return
    
    .org $13        ; INT2 Interrupt (External) or T0L Overflow
    jmp __nop_vec   ; Nothing to do for this interrupt, just return
    
    .org $1B        ; INT3 Interrupt (External) or Base Timer Overflow
    jmp __time_vec  ; Every time this interrupt is risen, call firmware to update time
    
    .org $23        ; T0H Overflow
    jmp __nop_vec   ; Nothing to do for this interrupt, just return
    
    .org $2B        ; T1H or T1L Overflow
    jmp __nop_vec   ; Nothing to do for this interrupt, just return
    
    .org $33        ; SIO0 Interrupt
    jmp __nop_vec   ; Nothing to do for this interrupt, just return
    
    .org $3B        ; SIO1 Interrupt
    jmp __nop_vec   ; Nothing to do for this interrupt, just return
    
    .org $43        ; RFB Interrupt
    jmp __nop_vec   ; Nothing to do for this interrupt, just return
    
    .org $4B        ; P3 Interrupt
    jmp __nop_vec   ; Nothing to do for this interrupt, just return

__nop_vec:
    reti            ; Just return, used for unnecessary ISRs
    
    .org $130       ; Firmware Entry Vector - Update System Time
__time_vec:
    push ie         ; Save current Interrupt Enable settings
    clr1 ie, 7      ; Block all maskable interrupts
    not1 ext, 0     ; NOT1 EXT, 0, followed by a jmpf to a vector
    jmpf __time_vec ; will call the firmware function, and when done, return here
    pop ie          ; Restore previous Interrupt Enable settings
    reti
    
    .org $1F0       ; Firmware Entry Vector - Leave Game Mode
__leave_vec:
    not1 ext, 0     ; Same as __time_vec; call the firmware
    jmpf __leave_vec

; -----------------------------------------------------------------------------
; VMS File Header
; -----------------------------------------------------------------------------
    .org $200       ; For games, header begins at offset $200
    
                    ; 16 bytes of file description (on the VMS)
    .text 16 "Demo by Shirobon"
                    ; 32 bytes of file description (on the Dreamcast)
    .text 32 "VMU ASM Hello World by Shirobon "
    
    .string 16 ""   ; Identifier of application that created the file (just skip)
    
                    ; Use nifty Waterbear directive to directly generate the icon data :)
    .include icon "icon.png"    
    
; -----------------------------------------------------------------------------
; Main Program Entry Point
; -----------------------------------------------------------------------------
    .org $680       ; Main entry point begins at $680
__main:
    clr1 ie, 7      ; Disable interrupts while we initialize hardware
    mov #$81, ocr   ; Setup Oscillation Control Register
                    ; Bit 7 - Clock Divisor. 0=Divide by 12, 1=Divide by 6
                    ; Bit 5 - Set Subclock Mode (32kHz), preserve battery
                    ; Bit 0 - Disable Main Clock. Set to 1 when VMU undocked
    mov #$09, mcr   ; Setup Mode Control Register (LCD Operation)
                    ; Bit 4 - Set to 0, set refresh rate to 83Hz
                    ; Bit 3 - Set to 1 to enable refresh (0 stops LCD)
                    ; Bit 0 - Set to 1 for graphics mode
    mov #$80, vccr  ; Setup LCD Contrast Control Register
                    ; Bit 7 - Set to 1 to enable LCD display (after enabling refresh)
    clr1 p3int, 0   ; Clear bit 0 in Port 3 Interrupt Control Register
                    ; This register sets up interrupts to happen on button press
                    ; Bit 0 - Enable interrupts (which sets bit 1 of p3int on press)
                    ; But, we won't use it (set to 0), because they suck ass
                    ; and don't report when the button was unpressed
    clr1 p1, 7      ; Clear bit 7 in Port 1 Latch
                    ; I don't know why this is necessary, Marcus documents don't
                    ; even show this bit is used for anything. But he does this 
                    ; in his example tetris demo.
    mov #$FF, p3    ; Port 3 is the buttons on the VMU. 0=pressed, 1=unpressed
                    ; Set all to unpressed
    
    clr1 psw, 4     ; Indirect address register bank bits of PSW register
    clr1 psw, 3     ; 00 makes R0-R3 correspond to address 000-003
                    ; Read the docs on indirect addressing
    
    mov #$82, $2    ; Address $2 in RAM stores the offset in which we will
                    ; indirectly address. Address $2 in RAM corresponds to the
                    ; $100 to $1FF range in SFR space.
                    ; Therefore, since we want to write to $182 in XRAM,
                    ; We put $82 in address $2, to designate a write to SFR $182
    mov #2, xbnk    ; LCD Framebuffer (XRAM) is divided into 3 banks. Bank 0 is the
                    ; upper half of LCD, 1 is the bottom half, and 2 is the
                    ; 4 icons on the bottom of the screen.
    xor acc         ; I don't like the game icon, so clear it by writing 0
                    ; to address $182 in bank 2
    st @R2          ; Write that shit.
    
    set1 ie, 7      ; Restore interrupts after initializing all hardware


    
.main:
    mov #<RUST, trl
    mov #>RUST, trh
    inc trl
    inc trl
    call __copytovf
    call __commitvf


.loop:
    jmp .loop
    
; -----------------------------------------------------------------------------
; __setvfpixel - Set Virtual Framebuffer Pixel at (x,y)
; Register b = X coordinate
; Register c = Y coordinate
; Clobbered: ACC, B, C, Work RAM Registers Modified
; Screen is 48x32 pixels. 1 bpp. 6 bytes horizontal.
; -----------------------------------------------------------------------------    
__setvfpixel:       ; Algorithm: Set ((y*6)+(x/8))th byte's (x%8)-1 pixel
    push b          ; Save X coordinate since B is used for multiplication
    xor acc         ; Clear accumulator
    mov #6, b       ; MUL is kinda reest. {ACC, C} form a 16 bit multiplicand
                    ; which is multiplied with register B to form a 24 bit
                    ; result in {B, ACC, C}
    mul             ; ACC = 0, C = Y coordinate. {B,ACC,C} contains Ycoord*6.
    pop acc         ; Restore X coordinate in accumulator and keep for transfer
    push c          ; Ycoord*6 will never be above 256, so just save lowest 8 bits
    st c            ; Move Xcoord from accumulator to C since it is the dividend
    xor acc         ; Division is also reest. 16-bit divident in {ACC,C}
    mov #8, b       ; 8-bit divisor in Register B
    div             ; Do {ACC,C}/B -> C contains X/8, B contains remainder
    xor acc         ; Clear accumulator just in case
    add c           ; Accumulator = X/8
    pop c           ; Restore multiplication result Ycoord*6
    add c           ; Accumulator is now ((Y*6) + (X/8)), the correct byte in framebuffer
    
    mov #$0, vrmad2 ; VRMAD (9 bit register) holds address of Work RAM (256 byte area)
    st vrmad1       ; which will be accessed through VTRBF (to hold virtual framebuffer)
                    ; Acc holds the byte offset of our virtual pixel group, 
                    ; so set VRMAD1 to that
    clr1 vsel, 4    ; VSEL Bit 4 - If set autoincrement VRMAD on every VRTBF access.
                    ; Disable it, we don't want autoincrement
                    
    ld b            ; Move B (contains the bit offset into the group of pixels)
                    ; to accumulator for comparison
                    
                    ; Because this reest processor doesn't have a way to programatically
                    ; set a bit, we gotta get a little stoopid.
                    ; RHYMEはお辞め、博士に任せ I GET STOOPID 涙の出る馬鹿さ加減
                    
.b0:bne #0, .b1     ; If 0, store in bit 7 (MSB, leftmost) , otherwise check again
    ld vtrbf        ; Load what is already in the virtual framebuffer to acc
    set1 acc, 7     ; Set the pixel
    st vtrbf        ; Store it back into virtual framebuffer
    ret
.b1:bne #1, .b2     ; If 1, store in bit 6 (next bit from MSB), otherwise check again
    ld vtrbf
    set1 acc, 6
    st vtrbf
    ret
.b2:bne #2, .b3     ; Keep doing this ...
    ld vtrbf
    set1 acc, 5
    st vtrbf
    ret
.b3:bne #3, .b4
    ld vtrbf
    set1 acc, 4
    st vtrbf
    ret
.b4:bne #4, .b5
    ld vtrbf
    set1 acc, 3
    st vtrbf
    ret
.b5:bne #5, .b6
    ld vtrbf
    set1 acc, 2
    st vtrbf
    ret
.b6:bne #6, .b7
    ld vtrbf
    set1 acc, 1
    st vtrbf
    ret
.b7:
    ld vtrbf
    set1 acc, 0
    st vtrbf
    ret

; -----------------------------------------------------------------------------
; __commitvf - Commit Virtual Framebuffer to Real Framebuffer
; Clobbered: Nothing
; -----------------------------------------------------------------------------
__commitvf:
    push acc        ; Save registers so the application code doesn't need to worry
    push xbnk
    push $2         ; Will use this as pointer for framebuffer
    push vsel
    push vrmad1
    push vrmad2

.begin:
    mov #$80, $2    ; Framebuffer starts at address $180, so we put $80 into $2 for
                    ; Indirect SFR addressing
    xor acc         ; Set acc to zero
    st xbnk         ; Select first half of framebuffer
    st vrmad1       ; Start at first byte in virtual framebuffer
    st vrmad2
    set1 vsel, 4    ; Enable autoincrement address for sequential copy

.loop:
    ld vtrbf        ; Get a byte from Work RAM Virtual Framebuffer
    st @R2          ; Store in real framebuffer
    inc $2          ; Increment to next framebuffer
    ld $2           ; Load value to accumulator for testing
    and #$0F        ; Test if address is divisible by 12
    bne #$0C, .skip ; Since after 2 lines (12 bytes) there are 4 empty bytes need to skip over
    ld $2           ; If address is divisible by 12 then we copied 2 lines
    add #4          ; So add 4 more to skip over the unused bytes
    st $2           ; This is the new address into the framebuffer
    bnz .skip       ; If the address was 0, we have rolled over past 256 and need to
    inc xbnk        ; write to the next bank (since 1 XBNK only contains half the framebuffer)
    mov #$80, $2    ; Reset framebuffer address to point to beginning of next bank
.skip:
    ld vrmad1       ; Get current Work RAM (Virtual Framebuffer) Address
    bne #$C0, .loop ; If we haven't copied the whole virtual framebuffer yet, go back and copy more
    
    pop vrmad2      ; Restore clobbered registers
    pop vrmad1
    pop vsel
    pop $2
    pop xbnk
    pop acc
    ret

; -----------------------------------------------------------------------------
; __copytovf - Copy a bitmap from TRH/TRL to Virtual Framebuffer
; Clobbered: Nothing
; -----------------------------------------------------------------------------
__copytovf:
    push acc        ; Save registers so the application code doesn't need to worry
    push c
    push vsel
    push vrmad1
    push vrmad2
    
    xor acc         ; Set initial counter to 0
    st c            ; Register C is counter
    st vrmad1       ; Start at first byte in virtual framebuffer
    st vrmad2
    set1 vsel, 4    ; Enable autoincrement address for sequential copy

.loop:
    ldc             ; Get a byte from [(trh:trl)+c]
    st vtrbf        ; Store the byte in virtual framebuffer, and autoincrement
    inc c           ; Increment counter and check if we copied 6*32 = $C0 bytes
    ld c
    bne #$C0, .loop ; If not, then copy more
.done:
    pop vrmad2      ; Restore clobbered registers
    pop vrmad1
    pop vsel
    pop c
    pop acc
    ret
    

RUST:
    .include sprite "rust.png"

    
; -----------------------------------------------------------------------------
; THE END, HOPE YOU ENJOYED! ;)
; -----------------------------------------------------------------------------
    .cnop 0, $200   ; Pad binary to an even number of blocks.
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
Posts: 10294
Joined: Thu May 20, 2004 2:04 pm
Current Project: Elysian Shadows
Favorite Gaming Platforms: Dreamcast, SNES, NES
Programming Language of Choice: C/++
Location: Studio Vorbis, AL
Contact:

Re: Humanitarian Action via the VMU

Post by Falco Girgis »

What is this goddamn abomination!?

Errrr... I mean, in order to protect the Chaos and their Gardens, it is of the utmost importance that only safe languages be allowed to run on the device.
Post Reply