~rabbits/nespaint

4851f03131903a0a15234a54d485b7c1586317f6 — Devine Lu Linvega 1 year, 2 months ago 1100f43
Implemented lin6
6 files changed, 1400 insertions(+), 1080 deletions(-)

M build.sh
A cart.nes
M src/cart.asm
M tools/asm6
M tools/lin6
M tools/lin6.c
M build.sh => build.sh +8 -11
@@ 14,7 14,7 @@ else
  tcc tools/asm6.c -o tools/asm6
fi

# If lint6 is not built, build it.
# If lin6 is not built, build it.
if test -f "tools/lin6"; then
  echo "Linter is ready."
else 


@@ 22,18 22,15 @@ else
  tcc tools/lin6.c -o tools/lin6
fi

# lin6
echo "Linting code."
tools/lin6 src/cart.asm
# Lint project
for filename in src/*.asm; do
    [ -e "$filename" ] || continue
   	tools/lin6 -i "$filename"
done

# asm6
# Build new cart
echo "Assembling new cart."
tools/asm6 src/cart.asm cart.nes

# Run
# fceux cart.nes --loadlua debug.lua

# Pushing
# ~/Applications/butler push cart.nes hundredrabbits/nespaint:main
# ~/Applications/butler push README hundredrabbits/nespaint:readme
# ~/Applications/butler status hundredrabbits/nespaint
fceux cart.nes --loadlua debug.lua

A cart.nes => cart.nes +0 -0
M src/cart.asm => src/cart.asm +958 -1052
@@ 1,1291 1,1197 @@

;; Header

  .db  "NES", $1a              ; identification of the iNES header
  .db  PRG_COUNT               ; number of 16KB PRG-ROM pages
  .db  $01                     ; number of 8KB CHR-ROM pages
  .db  $00                     ; NROM
  .dsb $09, $00                ; clear the remaining bytes
  .fillvalue $FF               ; Sets all unused space in rom to value $FF
	.db "NES", $1a ; identification of the iNES header
	.db 1 ; number of 16KB PRG-ROM pages
	.db $01 ; number of 8KB CHR-ROM pages
	.db $00 ; NROM
	.dsb $09,$00 ; clear the remaining bytes
	.fillvalue $FF ; Sets all unused space in rom to value $FF

;; Variables

  .enum $0000                  ; Zero Page variables
pos_x                   .dsb 1
pos_y                   .dsb 1
touch_x                 .dsb 1
touch_y                 .dsb 1
tool                    .dsb 1
rotation                .dsb 1
shape                   .dsb 1
mirror_x                .dsb 1
mirror_y                .dsb 1
mirror_xy               .dsb 1
color_fg                .dsb 1
color_bg                .dsb 1
palette_1               .dsb 1
palette_2               .dsb 1
palette_3               .dsb 1
palette_4               .dsb 1
is_accel_down           .dsb 1
is_paint_down           .dsb 1
is_select_down          .dsb 1
is_start_down           .dsb 1
temp_value              .dsb 1
timer                   .dsb 1
  .ende
	.enum $0000 ; Zero Page variables
pos_x                   .dsb 1 
pos_y                   .dsb 1 
touch_x                 .dsb 1 
touch_y                 .dsb 1 
tool                    .dsb 1 
rotation                .dsb 1 
shape                   .dsb 1 
mirror_x                .dsb 1 
mirror_y                .dsb 1 
mirror_xy               .dsb 1 
color_fg                .dsb 1 
color_bg                .dsb 1 
palette_1               .dsb 1 
palette_2               .dsb 1 
palette_3               .dsb 1 
palette_4               .dsb 1 
is_accel_down           .dsb 1 
is_paint_down           .dsb 1 
is_select_down          .dsb 1 
is_start_down           .dsb 1 
temp_value              .dsb 1 
timer                   .dsb 1 
	.ende 

;; Constants

PRG_COUNT              = 1     ; 1 = 16KB, 2 = 32KB
MIRRORING              = %0001
LOCK_TIME           .equ #$08
PPU_CONTROL         .equ $2000
PPU_MASK            .equ $2001
PPU_STATUS          .equ $2002
PPU_SCROLL          .equ $2005
PPU_ADDRESS         .equ $2006
PPU_DATA            .equ $2007
  .org $C000
LOCK_TIME           .equ #$08 
PPU_CONTROL         .equ $2000 
PPU_MASK            .equ $2001 
PPU_STATUS          .equ $2002 
PPU_SCROLL          .equ $2005 
PPU_ADDRESS         .equ $2006 
PPU_DATA            .equ $2007 
	.org $C000

;; Reset

RESET:                         ; 
  SEI                          ; disable IRQs
  CLD                          ; disable decimal mode
  LDX #$40
  STX $4017                    ; disable APU frame IRQ
  LDX #$FF
  TXS                          ; Set up stack
  INX                          ; now X = 0
  STX PPU_CONTROL              ; disable NMI
  STX PPU_MASK                 ; disable rendering
  STX $4010                    ; disable DMC IRQs
RESET:                         ;
	SEI                          ; disable IRQs
	CLD                          ; disable decimal mode
	LDX #$40
	STX $4017                    ; disable APU frame IRQ
	LDX #$FF
	TXS                          ; Set up stack
	INX                          ; now X = 0
	STX PPU_CONTROL              ; disable NMI
	STX PPU_MASK                 ; disable rendering
	STX $4010                    ; disable DMC IRQs

Vblankwait1:                   ; First wait for vblank to make sure PPU is ready
  BIT PPU_STATUS
  BPL Vblankwait1
Clrmem:                        ; 
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0300, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0200, x                 ; move all sprites off screen
  INX
  BNE Clrmem
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
  BIT PPU_STATUS
  BPL Vblankwait2
	BIT PPU_STATUS
	BPL Vblankwait1

Clrmem:                        ;
	LDA #$00
	STA $0000, x
	STA $0100, x
	STA $0300, x
	STA $0400, x
	STA $0500, x
	STA $0600, x
	STA $0700, x
	LDA #$FE
	STA $0200, x                 ; move all sprites off screen
	INX 
	BNE Clrmem

;;
Vblankwait2:                   ; Second wait for vblank, PPU is ready after this
	BIT PPU_STATUS
	BPL Vblankwait2

LoadPalettes:                  ; [skip]
  LDA PPU_STATUS
  LDA #$3F
  STA PPU_ADDRESS
  LDA #$00
  STA PPU_ADDRESS
  LDX #$00
@loop:                         ; 
  LDA palettes, x
  STA PPU_DATA
  INX
  CPX #$20
  BNE @loop

;;
	LDA PPU_STATUS
	LDA #$3F
	STA PPU_ADDRESS
	LDA #$00
	STA PPU_ADDRESS
	LDX #$00
@loop:                         ;
	LDA palettes, x
	STA PPU_DATA
	INX 
	CPX #$20
	BNE @loop

LoadAttributes:                ; [skip]
  LDA PPU_STATUS
  LDA #$23
  STA PPU_ADDRESS
  LDA #$C0
  STA PPU_ADDRESS
  LDX #$00
@loop:                         ; 
  LDA #$00
  STA PPU_DATA
  INX
  CPX #$40
  BNE @loop

;;
	LDA PPU_STATUS
	LDA #$23
	STA PPU_ADDRESS
	LDA #$C0
	STA PPU_ADDRESS
	LDX #$00
@loop:                         ;
	LDA #$00
	STA PPU_DATA
	INX 
	CPX #$40
	BNE @loop

SetupInterface:                ; [skip]
  LDA PPU_STATUS
  LDX #$00
@loop:                         ; 
  LDA tools_lb, x
  STA PPU_ADDRESS
  LDA tools_hb, x
  STA PPU_ADDRESS
  TXA
  CLC
  ADC #$C0
  STA PPU_DATA
  INX
  CPX #$10
  BNE @loop
	LDA PPU_STATUS
	LDX #$00
@loop:                         ;
	LDA tools_lb, x
	STA PPU_ADDRESS
	LDA tools_hb, x
	STA PPU_ADDRESS
	TXA 
	CLC 
	ADC #$C0
	STA PPU_DATA
	INX 
	CPX #$10
	BNE @loop

;; Add cursor origin

  LDA #$88
  STA pos_x
  LDA #$88
  STA pos_y
	LDA #$88
	STA pos_x
	LDA #$88
	STA pos_y

;; Create sprite

  LDA pos_y
  STA $0200                    ; set tile.y pos
  LDA #$01
  STA $0201                    ; set tile.id
  LDA #$00
  STA $0202                    ; set tile.attribute
  LDA pos_x
  STA $0203                    ; set tile.x pos
	LDA pos_y
	STA $0200                    ; set tile.y pos
	LDA #$01
	STA $0201                    ; set tile.id
	LDA #$00
	STA $0202                    ; set tile.attribute
	LDA pos_x
	STA $0203                    ; set tile.x pos

;; Create helper sprite

  LDA #$C0
  STA $0204                    ; set tile.y pos
  LDA #$21
  STA $0205                    ; set tile.id
  LDA #$00
  STA $0206                    ; set tile.attribute
  LDA #$08
  STA $0207                    ; set tile.x pos
	LDA #$C0
	STA $0204                    ; set tile.y pos
	LDA #$21
	STA $0205                    ; set tile.id
	LDA #$00
	STA $0206                    ; set tile.attribute
	LDA #$08
	STA $0207                    ; set tile.x pos

;; Create directory sprite

  LDA #$C8
  STA $0208                    ; set tile.y pos
  LDA #$08
  STA $0209                    ; set tile.id
  LDA #$00
  STA $020a                    ; set tile.attribute
  LDA #$08
  STA $020b                    ; set tile.x pos
	LDA #$C8
	STA $0208                    ; set tile.y pos
	LDA #$08
	STA $0209                    ; set tile.id
	LDA #$00
	STA $020a                    ; set tile.attribute
	LDA #$08
	STA $020b                    ; set tile.x pos

;; Create starting palette($0F,$28,$3B,$30)

  LDA #$0F
  STA palette_1
  LDA #$28
  STA palette_2
  LDA #$3B
  STA palette_3
  LDA #$30
  STA palette_4
	LDA #$0F
	STA palette_1
	LDA #$28
	STA palette_2
	LDA #$3B
	STA palette_3
	LDA #$30
	STA palette_4

;; Select the starting color

  LDY #$01
  JSR SelectFg@color
  LDY #$00
  JSR SelectBg@color
  JSR RedrawCursor@control
	LDY #$01
	JSR SelectFg@color
	LDY #$00
	JSR SelectBg@color
	JSR RedrawCursor@control

;; Select the starting shape

  LDY #$00
  JSR Select@shape
  LDY #$01
  JSR Select@shape
	LDY #$00
	JSR Select@shape
	LDY #$01
	JSR Select@shape

;; Enable sprites

  LDA #%10010000               ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  STA PPU_CONTROL
  LDA #%00011110               ; enable sprites, enable background, no clipping on left side
  STA PPU_MASK
  LDA #$00                     ; No background scrolling
  STA PPU_ADDRESS
  STA PPU_ADDRESS
  STA PPU_SCROLL
  STA PPU_SCROLL
	LDA #%10010000               ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
	STA PPU_CONTROL
	LDA #%00011110               ; enable sprites, enable background, no clipping on left side
	STA PPU_MASK
	LDA #$00                     ; No background scrolling
	STA PPU_ADDRESS
	STA PPU_ADDRESS
	STA PPU_SCROLL
	STA PPU_SCROLL

;;
Forever:                       ;
	JMP Forever                  ; jump back to Forever, infinite loop

Forever:                       ; 
  JMP Forever                  ; jump back to Forever, infinite loop
;; NMI

NMI:
  LDA #$00

  STA $2003                    ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014                    ; set the high byte (02) of the RAM address, start the transfer
  ; check lock timer
  LDA timer
  CMP #$00
  BEQ @continue
  DEC timer
  JMP @skip
	LDA #$00
	STA $2003                    ; set the low byte (00) of the RAM address
	LDA #$02
	STA $4014                    ; set the high byte (02) of the RAM address, start the transfer
	; check lock timer
	LDA timer
	CMP #$00
	BEQ @continue
	DEC timer
	JMP @skip
@continue:
  LDA #$00                     ; reset flags
  STA is_accel_down
  STA is_paint_down
  STA is_select_down
  STA is_start_down
	LDA #$00                     ; reset flags
	STA is_accel_down
	STA is_paint_down
	STA is_select_down
	STA is_start_down
@latch:
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016                    ; tell both the controllers to latch buttons
@ReadA: 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ @ReadB
  LDA #$01
  STA is_accel_down
@ReadB: 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ @ReadSel
  LDA #$01
  STA is_paint_down            ; set cursor state
  JSR Touch
  JSR Rotate@control
  JSR Lock@control
@ReadSel: 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ @ReadStart
  LDA #$01
  STA is_select_down
@ReadStart: 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ @ReadUp
  LDA #$01
  STA is_start_down
@ReadUp: 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ @ReadDown
  JSR PressUp@control
  JSR Unlock@control
@ReadDown: 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ @ReadLeft
  JSR PressDown@control
  JSR Unlock@control
@ReadLeft: 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ @ReadRight
  JSR PressLeft@control
  JSR Unlock@control
@ReadRight: 
  LDA $4016
  AND #%00000001               ; only look at BIT 0
  BEQ @Done
  JSR PressRight@control
  JSR Unlock@control
	LDA #$01
	STA $4016
	LDA #$00
	STA $4016                    ; tell both the controllers to latch buttons
@ReadA:
	LDA $4016
	AND #%00000001               ; only look at BIT 0
	BEQ @ReadB
	LDA #$01
	STA is_accel_down
@ReadB:
	LDA $4016
	AND #%00000001               ; only look at BIT 0
	BEQ @ReadSel
	LDA #$01
	STA is_paint_down            ; set cursor state
	JSR Touch
	JSR Rotate@control
	JSR Lock@control
@ReadSel:
	LDA $4016
	AND #%00000001               ; only look at BIT 0
	BEQ @ReadStart
	LDA #$01
	STA is_select_down
@ReadStart:
	LDA $4016
	AND #%00000001               ; only look at BIT 0
	BEQ @ReadUp
	LDA #$01
	STA is_start_down
@ReadUp:
	LDA $4016
	AND #%00000001               ; only look at BIT 0
	BEQ @ReadDown
	JSR PressUp@control
	JSR Unlock@control
@ReadDown:
	LDA $4016
	AND #%00000001               ; only look at BIT 0
	BEQ @ReadLeft
	JSR PressDown@control
	JSR Unlock@control
@ReadLeft:
	LDA $4016
	AND #%00000001               ; only look at BIT 0
	BEQ @ReadRight
	JSR PressLeft@control
	JSR Unlock@control
@ReadRight:
	LDA $4016
	AND #%00000001               ; only look at BIT 0
	BEQ @Done
	JSR PressRight@control
	JSR Unlock@control
@Done:                         ; handling this button is done
  JSR UpdateCursor
  LDA PPU_STATUS
  LDA #$00                     ; No background scrolling
  STA PPU_SCROLL
  STA PPU_SCROLL
	JSR UpdateCursor
	LDA PPU_STATUS
	LDA #$00                     ; No background scrolling
	STA PPU_SCROLL
	STA PPU_SCROLL
@skip:
  RTI                          ; return from interrupt

;;
	RTI                          ; return from interrupt

Touch:
  ; convert pixel pos to tile pos
  LDX pos_x
  STX touch_x
  LSR touch_x
  LSR touch_x
  LSR touch_x
  ; convert pixel pos to tile pos
  LDY pos_y
  STY touch_y
  LSR touch_y
  LSR touch_y
  LSR touch_y
  LDA touch_x
  CMP #$01                     ; don't paint on x of interface
  BNE @DoPaint
  ; find tool id
  LDA touch_y
  SEC
  SBC #$08                     ; tool vertical offset
  STA tool
  ; find family id
  TAY
  LDA families, y
  CMP #$00
  BEQ Touch@brush
  CMP #$01
  BEQ Touch@mirror
  CMP #$02
  BEQ Touch@shape
  CMP #$03
  BEQ Touch@color
	; convert pixel pos to tile pos
	LDX pos_x
	STX touch_x
	LSR touch_x
	LSR touch_x
	LSR touch_x
	; convert pixel pos to tile pos
	LDY pos_y
	STY touch_y
	LSR touch_y
	LSR touch_y
	LSR touch_y
	LDA touch_x
	CMP #$01                     ; don't paint on x of interface
	BNE @DoPaint
	; find tool id
	LDA touch_y
	SEC 
	SBC #$08                     ; tool vertical offset
	STA tool
	; find family id
	TAY 
	LDA families, y
	CMP #$00
	BEQ Touch@brush
	CMP #$01
	BEQ Touch@mirror
	CMP #$02
	BEQ Touch@shape
	CMP #$03
	BEQ Touch@color
@DoPaint:
  JSR Paint
	JSR Paint
@CheckX:                       ; Check mirror modes
  LDA mirror_x
  CMP #$01
  BNE @CheckY
  JSR PaintMirrorX
	LDA mirror_x
	CMP #$01
	BNE @CheckY
	JSR PaintMirrorX
@CheckY:                       ; Check mirror modes
  LDA mirror_y
  CMP #$01
  BNE @CheckXY
  JSR PaintMirrorY
	LDA mirror_y
	CMP #$01
	BNE @CheckXY
	JSR PaintMirrorY
@CheckXY:                      ; Check mirror modes
  LDA mirror_xy
  CMP #$01
  BNE @done
  JSR PaintMirrorXY
	LDA mirror_xy
	CMP #$01
	BNE @done
	JSR PaintMirrorXY
@done:
  RTS
	RTS 

;; BRUSH ======================

Touch@brush:                   ; (y:id)
  CPY #$00
  BEQ SelectPaint@brush
  CPY #$01
  BEQ SelectEraser@brush
  CPY #$02
  BEQ SelectLine@brush
  CPY #$03
  BEQ SelectOverlay@brush
  RTS

;;
	CPY #$00
	BEQ SelectPaint@brush
	CPY #$01
	BEQ SelectEraser@brush
	CPY #$02
	BEQ SelectLine@brush
	CPY #$03
	BEQ SelectOverlay@brush
	RTS 

Touch@mirror:                  ; (y:id)
  TYA                          ; prepare offset
  SEC
  SBC #$04                     ; offset to 0-4
  TAY
  JSR Toggle@mirror
  RTS

;;
	TYA                          ; prepare offset
	SEC 
	SBC #$04                     ; offset to 0-4
	TAY 
	JSR Toggle@mirror
	RTS 

Touch@shape:                   ; (y:shape)
  TYA                          ; prepare offset
  SEC
  SBC #$08                     ; offset to 0-4
  CMP shape                    ; check if changed
  BEQ @skip
  TAY
  JSR Select@shape
	TYA                          ; prepare offset
	SEC 
	SBC #$08                     ; offset to 0-4
	CMP shape                    ; check if changed
	BEQ @skip
	TAY 
	JSR Select@shape
@skip:
  RTS

;;
	RTS 

Touch@color:                   ; (y:color)
  TYA                          ; prepare offset
  SEC
  SBC #$0C                     ; offset to 0-4
  CMP color_fg                 ; check if changed
  BEQ @skip
  STA color_fg                 ; set
  JSR Redraw@color
  JSR RedrawCursor@control
  JSR Redraw@helper
	TYA                          ; prepare offset
	SEC 
	SBC #$0C                     ; offset to 0-4
	CMP color_fg                 ; check if changed
	BEQ @skip
	STA color_fg                 ; set
	JSR Redraw@color
	JSR RedrawCursor@control
	JSR Redraw@helper
@skip:
  RTS

;;
	RTS 

SelectPaint@brush:
  LDY #$01
  JSR SelectFg@color
  LDY #$00
  JSR SelectBg@color
  JSR RedrawCursor@control
  JSR Lock@control
  RTS

;;
	LDY #$01
	JSR SelectFg@color
	LDY #$00
	JSR SelectBg@color
	JSR RedrawCursor@control
	JSR Lock@control
	RTS 

SelectEraser@brush:
  LDY #$00
  JSR SelectFg@color
  LDY #$00
  JSR SelectBg@color
  JSR RedrawCursor@control
  JSR Lock@control
  RTS

;;
	LDY #$00
	JSR SelectFg@color
	LDY #$00
	JSR SelectBg@color
	JSR RedrawCursor@control
	JSR Lock@control
	RTS 

SelectLine@brush:
  LDY #$01
  JSR SelectFg@color
  LDY #$01
  JSR SelectBg@color
  JSR RedrawCursor@control
  JSR Lock@control
  RTS

;;
	LDY #$01
	JSR SelectFg@color
	LDY #$01
	JSR SelectBg@color
	JSR RedrawCursor@control
	JSR Lock@control
	RTS 

SelectOverlay@brush:
  LDY #$01
  JSR SelectFg@color
  LDY #$02
  JSR SelectBg@color
  JSR RedrawCursor@control
  JSR Lock@control
  RTS
	LDY #$01
	JSR SelectFg@color
	LDY #$02
	JSR SelectBg@color
	JSR RedrawCursor@control
	JSR Lock@control
	RTS 

;; MIRROR =====================

Toggle@mirror:                 ; (y:shape)
  CPY #$00
  BEQ ToggleX@mirror
  CPY #$01
  BEQ ToggleY@mirror
  CPY #$02
  BEQ ToggleXY@mirror
  RTS

;;
	CPY #$00
	BEQ ToggleX@mirror
	CPY #$01
	BEQ ToggleY@mirror
	CPY #$02
	BEQ ToggleXY@mirror
	RTS 

ToggleX@mirror:
  LDA mirror_x
  CMP #$00
  BEQ @toggle
  LDA #$00
  JMP @save
	LDA mirror_x
	CMP #$00
	BEQ @toggle
	LDA #$00
	JMP @save
@toggle:
  LDA #$01
	LDA #$01
@save:
  STA mirror_x
	STA mirror_x
@redraw:
  TAY
  LDX #$00
  LDA mirrors_lb, x
  STA PPU_ADDRESS
  LDA mirrors_hb, x
  STA PPU_ADDRESS
  LDA mirrors_x_toggle, y
  STA PPU_DATA
  JSR Lock@control
  RTS

;;
	TAY 
	LDX #$00
	LDA mirrors_lb, x
	STA PPU_ADDRESS
	LDA mirrors_hb, x
	STA PPU_ADDRESS
	LDA mirrors_x_toggle, y
	STA PPU_DATA
	JSR Lock@control
	RTS 

ToggleY@mirror:
  LDA mirror_y
  CMP #$00
  BEQ @toggle
  LDA #$00
  JMP @save
	LDA mirror_y
	CMP #$00
	BEQ @toggle
	LDA #$00
	JMP @save
@toggle:
  LDA #$01
	LDA #$01
@save:
  STA mirror_y
	STA mirror_y
@redraw:
  TAY
  LDX #$01
  LDA mirrors_lb, x
  STA PPU_ADDRESS
  LDA mirrors_hb, x
  STA PPU_ADDRESS
  LDA mirrors_y_toggle, y
  STA PPU_DATA
  JSR Lock@control
  RTS

;;
	TAY 
	LDX #$01
	LDA mirrors_lb, x
	STA PPU_ADDRESS
	LDA mirrors_hb, x
	STA PPU_ADDRESS
	LDA mirrors_y_toggle, y
	STA PPU_DATA
	JSR Lock@control
	RTS 

ToggleXY@mirror:
  LDA mirror_xy
  CMP #$00
  BEQ @toggle
  LDA #$00
  JMP @save
	LDA mirror_xy
	CMP #$00
	BEQ @toggle
	LDA #$00
	JMP @save
@toggle:
  LDA #$01
	LDA #$01
@save:
  STA mirror_xy
	STA mirror_xy
@redraw:
  TAY
  LDX #$02
  LDA mirrors_lb, x
  STA PPU_ADDRESS
  LDA mirrors_hb, x
  STA PPU_ADDRESS
  LDA mirrors_xy_toggle, y
  STA PPU_DATA
  JSR Lock@control
  RTS
	TAY 
	LDX #$02
	LDA mirrors_lb, x
	STA PPU_ADDRESS
	LDA mirrors_hb, x
	STA PPU_ADDRESS
	LDA mirrors_xy_toggle, y
	STA PPU_DATA
	JSR Lock@control
	RTS 

;; SHAPE ======================

Select@shape:                  ; (y:shape)
  CPY shape                    ; check if changed
  BEQ @skip
  ; deselect previous shape
  LDX shape
  LDA shapes_lb, x
  STA PPU_ADDRESS
  LDA shapes_hb, x
  STA PPU_ADDRESS
  LDA shapes_off, x
  STA PPU_DATA
  ; select new shape
  STY shape
  LDA shapes_lb, y
  STA PPU_ADDRESS
  LDA shapes_hb, y
  STA PPU_ADDRESS
  LDA shapes_on, y
  STA PPU_DATA
	CPY shape                    ; check if changed
	BEQ @skip
	; deselect previous shape
	LDX shape
	LDA shapes_lb, x
	STA PPU_ADDRESS
	LDA shapes_hb, x
	STA PPU_ADDRESS
	LDA shapes_off, x
	STA PPU_DATA
	; select new shape
	STY shape
	LDA shapes_lb, y
	STA PPU_ADDRESS
	LDA shapes_hb, y
	STA PPU_ADDRESS
	LDA shapes_on, y
	STA PPU_DATA
@skip:
  RTS
	RTS 

;; COLOR =====================

SelectFg@color:                ; (y:new color)
  CPY color_fg                 ; check if changed
  BEQ @skip
  STY color_fg                 ; set
  JSR Redraw@color
  JSR Redraw@helper
	CPY color_fg                 ; check if changed
	BEQ @skip
	STY color_fg                 ; set
	JSR Redraw@color
	JSR Redraw@helper
@skip:
  RTS

;;
	RTS 

SelectBg@color:                ; (y:new color)
  CPY color_bg                 ; check if changed
  BEQ @skip
  STY color_bg                 ; set
  JSR Redraw@color
  JSR Redraw@helper
	CPY color_bg                 ; check if changed
	BEQ @skip
	STY color_bg                 ; set
	JSR Redraw@color
	JSR Redraw@helper
@skip:
  RTS

;;
	RTS 

Redraw@color:
  LDY #$00
  JSR RedrawSwatch@color
  LDY #$01
  JSR RedrawSwatch@color
  LDY #$02
  JSR RedrawSwatch@color
  LDY #$03
  JSR RedrawSwatch@color
  RTS

;;
	LDY #$00
	JSR RedrawSwatch@color
	LDY #$01
	JSR RedrawSwatch@color
	LDY #$02
	JSR RedrawSwatch@color
	LDY #$03
	JSR RedrawSwatch@color
	RTS 

RedrawSwatch@color:            ; (y:color)
  LDA colors_lb, y
  STA PPU_ADDRESS
  LDA colors_hb, y
  STA PPU_ADDRESS
  CPY color_fg                 ; is fg
  BEQ @foreground
  CPY color_bg                 ; is bg
  BEQ @background
  LDA colors_sprites, y        ; else
  JMP @save
	LDA colors_lb, y
	STA PPU_ADDRESS
	LDA colors_hb, y
	STA PPU_ADDRESS
	CPY color_fg                 ; is fg
	BEQ @foreground
	CPY color_bg                 ; is bg
	BEQ @background
	LDA colors_sprites, y        ; else
	JMP @save
@background:
  LDA colors_sprites_bg, y
  JMP @save
	LDA colors_sprites_bg, y
	JMP @save
@foreground:
  LDA colors_sprites_fg, y
  JMP @save
	LDA colors_sprites_fg, y
	JMP @save
@save:
  STA PPU_DATA
  RTS
	STA PPU_DATA
	RTS 

;; CONTROL ====================

PressUp@control:
  ; read modifiers
  LDA is_select_down
  CMP #$01
  BEQ NextShape@control
  LDA is_start_down
  CMP #$01
  BEQ NextFg@control
  ; defaults
  LDY #$00                     ; send to read-down as dir
  JSR SetRotation@control
  ; defaults
  DEC pos_y
  LDA is_accel_down
  CMP #$01
  BNE @done
  DEC pos_y
	; read modifiers
	LDA is_select_down
	CMP #$01
	BEQ NextShape@control
	LDA is_start_down
	CMP #$01
	BEQ NextFg@control
	; defaults
	LDY #$00                     ; send to read-down as dir
	JSR SetRotation@control
	; defaults
	DEC pos_y
	LDA is_accel_down
	CMP #$01
	BNE @done
	DEC pos_y
@done:
  RTS

;;
	RTS 

NextShape@control:
  LDA shape
  SEC
  SBC #$01
  CMP #$FF
  BNE @done
  LDA #$02
	LDA shape
	SEC 
	SBC #$01
	CMP #$FF
	BNE @done
	LDA #$02
@done:
  TAY
  JSR Select@shape
  JSR Lock@control
  RTS

;;
	TAY 
	JSR Select@shape
	JSR Lock@control
	RTS 

NextFg@control:
  LDA color_fg
  SEC
  SBC #$01
  CMP #$FF
  BNE @done
  LDA #$03
	LDA color_fg
	SEC 
	SBC #$01
	CMP #$FF
	BNE @done
	LDA #$03
@done:
  TAY
  JSR SelectFg@color
  JSR Lock@control
  JSR RedrawCursor@control
  RTS

;;
	TAY 
	JSR SelectFg@color
	JSR Lock@control
	JSR RedrawCursor@control
	RTS 

PressDown@control:
  ; read modifiers
  LDA is_select_down
  CMP #$01
  BEQ PrevShape@control
  LDA is_start_down
  CMP #$01
  BEQ PrevFg@control
  ; defaults
  LDY #$02                     ; send to read-down as dir
  JSR SetRotation@control
  ; defaults
  INC pos_y
  LDA is_accel_down
  CMP #$01
  BNE @done
  INC pos_y
	; read modifiers
	LDA is_select_down
	CMP #$01
	BEQ PrevShape@control
	LDA is_start_down
	CMP #$01
	BEQ PrevFg@control
	; defaults
	LDY #$02                     ; send to read-down as dir
	JSR SetRotation@control
	; defaults
	INC pos_y
	LDA is_accel_down
	CMP #$01
	BNE @done
	INC pos_y
@done:
  RTS

;;
	RTS 

PrevShape@control:
  LDA shape
  CLC
  ADC #$01
  CMP #$03
  BNE @done
  LDA #$00
	LDA shape
	CLC 
	ADC #$01
	CMP #$03
	BNE @done
	LDA #$00
@done:
  TAY
  JSR Select@shape
  JSR Lock@control
  RTS

;;
	TAY 
	JSR Select@shape
	JSR Lock@control
	RTS 

PrevFg@control:
  LDA color_fg
  CLC
  ADC #$01
  CMP #$04
  BNE @done
  LDA #$00
	LDA color_fg
	CLC 
	ADC #$01
	CMP #$04
	BNE @done
	LDA #$00
@done:
  TAY
  JSR SelectFg@color
  JSR Lock@control
  JSR RedrawCursor@control
  RTS

;;
	TAY 
	JSR SelectFg@color
	JSR Lock@control
	JSR RedrawCursor@control
	RTS 

PressLeft@control:
 ; read modifiers
  LDA is_start_down
  CMP #$01
  BEQ PrevBg@control
  LDA is_select_down
  CMP #$01
  BEQ PrevPalette@control
  ; defaults
  LDY #$03                     ; send to read-down as dir
  JSR SetRotation@control
  ; defaults
  DEC pos_x
  LDA is_accel_down
  CMP #$01
  BNE @done
  DEC pos_x
	; read modifiers
	LDA is_start_down
	CMP #$01
	BEQ PrevBg@control
	LDA is_select_down
	CMP #$01
	BEQ PrevPalette@control
	; defaults
	LDY #$03                     ; send to read-down as dir
	JSR SetRotation@control
	; defaults
	DEC pos_x
	LDA is_accel_down
	CMP #$01
	BNE @done
	DEC pos_x
@done:
  RTS

;;
	RTS 

PrevBg@control:
  LDA color_bg
  CLC
  ADC #$01
  CMP #$04
  BNE @done
  LDA #$00
	LDA color_bg
	CLC 
	ADC #$01
	CMP #$04
	BNE @done
	LDA #$00
@done:
  TAY
  JSR SelectBg@color
  JSR Lock@control
  JSR RedrawCursor@control
  RTS

;;
	TAY 
	JSR SelectBg@color
	JSR Lock@control
	JSR RedrawCursor@control
	RTS 

PrevPalette@control:
  LDA PPU_STATUS
  ; background
  LDA #$3F
  STA PPU_ADDRESS
  LDA color_fg
  STA PPU_ADDRESS
  LDX color_fg
  LDA palette_1, x
  SEC
  SBC #$01
  STA PPU_DATA
  STA palette_1, x
  ; foreground
  LDA #$3F
  STA PPU_ADDRESS
  LDA color_fg
  CLC
  ADC #$10
  STA PPU_ADDRESS
  LDX color_fg
  LDA palette_1, x
  STA PPU_DATA
  STA palette_1, x
  JSR Lock@control
  JSR RedrawSwatch@color
  RTS

;;
	LDA PPU_STATUS
	; background
	LDA #$3F
	STA PPU_ADDRESS
	LDA color_fg
	STA PPU_ADDRESS
	LDX color_fg
	LDA palette_1, x
	SEC 
	SBC #$01
	STA PPU_DATA
	STA palette_1, x
	; foreground
	LDA #$3F
	STA PPU_ADDRESS
	LDA color_fg
	CLC 
	ADC #$10
	STA PPU_ADDRESS
	LDX color_fg
	LDA palette_1, x
	STA PPU_DATA
	STA palette_1, x
	JSR Lock@control
	JSR RedrawSwatch@color
	RTS 

PressRight@control:
  ; read modifiers
  LDA is_start_down
  CMP #$01
  BEQ NextBg@control
  LDA is_select_down
  CMP #$01
  BEQ NextPalette@control
  ; defaults
  LDY #$01                     ; send to read-down as dir
  JSR SetRotation@control
  ; defaults
  INC pos_x
  LDA is_accel_down
  CMP #$01
  BNE @done
  INC pos_x
	; read modifiers
	LDA is_start_down
	CMP #$01
	BEQ NextBg@control
	LDA is_select_down
	CMP #$01
	BEQ NextPalette@control
	; defaults
	LDY #$01                     ; send to read-down as dir
	JSR SetRotation@control
	; defaults
	INC pos_x
	LDA is_accel_down
	CMP #$01
	BNE @done
	INC pos_x
@done:
  RTS

;;
	RTS 

NextBg@control:
  LDA color_bg
  SEC
  SBC #$01
  CMP #$FF
  BNE @done
  LDA #$03
	LDA color_bg
	SEC 
	SBC #$01
	CMP #$FF
	BNE @done
	LDA #$03
@done:
  TAY
  JSR SelectBg@color
  JSR Lock@control
  JSR RedrawCursor@control
  RTS

;;
	TAY 
	JSR SelectBg@color
	JSR Lock@control
	JSR RedrawCursor@control
	RTS 

NextPalette@control:
  LDA PPU_STATUS
  ; background
  LDA #$3F
  STA PPU_ADDRESS
  LDA color_fg
  STA PPU_ADDRESS
  ; increment palette
  LDX color_fg
  LDA palette_1, x
  CLC
  ADC #$01
  STA PPU_DATA
  STA palette_1, x
  ; foreground
  LDA #$3F
  STA PPU_ADDRESS
  LDA color_fg
  CLC
  ADC #$10
  STA PPU_ADDRESS
  LDX color_fg
  LDA palette_1, x
  STA PPU_DATA
  STA palette_1, x
  JSR Lock@control
  JSR RedrawSwatch@color
  RTS

;;
	LDA PPU_STATUS
	; background
	LDA #$3F
	STA PPU_ADDRESS
	LDA color_fg
	STA PPU_ADDRESS
	; increment palette
	LDX color_fg
	LDA palette_1, x
	CLC 
	ADC #$01
	STA PPU_DATA
	STA palette_1, x
	; foreground
	LDA #$3F
	STA PPU_ADDRESS
	LDA color_fg
	CLC 
	ADC #$10
	STA PPU_ADDRESS
	LDX color_fg
	LDA palette_1, x
	STA PPU_DATA
	STA palette_1, x
	JSR Lock@control
	JSR RedrawSwatch@color
	RTS 

Lock@control:
  LDA LOCK_TIME
  STA timer
  RTS

;;
	LDA LOCK_TIME
	STA timer
	RTS 

Unlock@control:
  LDA is_select_down
  CMP #$01
  BEQ @skip
  LDA is_start_down
  CMP #$01
  BEQ @skip
  LDA #$00
  STA timer
	LDA is_select_down
	CMP #$01
	BEQ @skip
	LDA is_start_down
	CMP #$01
	BEQ @skip
	LDA #$00
	STA timer
@skip:
  RTS

;;
	RTS 

RedrawCursor@control:
  LDA PPU_STATUS               ; 
  LDA color_fg
  ASL A
  ASL A
  CLC
  ADC #$10
  ADC color_bg
  STA $0201                    ; set tile.id
  RTS

;;
	LDA PPU_STATUS               ;
	LDA color_fg
	ASL A
	ASL A
	CLC 
	ADC #$10
	ADC color_bg
	STA $0201                    ; set tile.id
	RTS 

Redraw@helper:
  LDA PPU_STATUS               ; 
  LDA color_fg
  ASL A
  ASL A
  CLC
  ADC #$20
  ADC color_bg
  STA $0205                    ; set tile.id
  RTS

;;
	LDA PPU_STATUS               ;
	LDA color_fg
	ASL A
	ASL A
	CLC 
	ADC #$20
	ADC color_bg
	STA $0205                    ; set tile.id
	RTS 

Redraw@directory:
  LDA PPU_STATUS               ; 
  LDA rotation
  CLC
  ADC #$08
  STA $0209                    ; set tile.id
  RTS

;;
	LDA PPU_STATUS               ;
	LDA rotation
	CLC 
	ADC #$08
	STA $0209                    ; set tile.id
	RTS 

SetRotation@control:           ; (y:dir)
  LDA is_accel_down
  CMP #$01                     ; is b is down
  BEQ @skip                    ; skip rot store
  STY rotation
	LDA is_accel_down
	CMP #$01                     ; is b is down
	BEQ @skip                    ; skip rot store
	STY rotation
@skip:
  JSR Redraw@directory
  RTS

;;
	JSR Redraw@directory
	RTS 

Rotate@control:
  LDA is_accel_down
  CMP #$01
  BEQ @skip
  INC rotation
  LDA rotation
  CMP #$04
  BNE @done
  LDA #$00
  STA rotation
	LDA is_accel_down
	CMP #$01
	BEQ @skip
	INC rotation
	LDA rotation
	CMP #$04
	BNE @done
	LDA #$00
	STA rotation
@done:
  JSR Redraw@directory
	JSR Redraw@directory
@skip:
  RTS

;;
	RTS 

UpdateCursor:
  ; move cursor sprite
  LDA pos_x                    ; load pos x
  STA $0203                    ; set x pos
  LDA pos_y                    ; load pos y
  STA $0200                    ; set y pos
  LDA is_start_down
  CMP #$01
  BEQ @startState              ; jump to state change
  LDA is_select_down
  CMP #$01
  BEQ @selectState             ; jump to state change
  LDA is_paint_down
  CMP #$01
  BEQ @paintState              ; jump to state change
  LDA #$00
  JMP @save
	; move cursor sprite
	LDA pos_x                    ; load pos x
	STA $0203                    ; set x pos
	LDA pos_y                    ; load pos y
	STA $0200                    ; set y pos
	LDA is_start_down
	CMP #$01
	BEQ @startState              ; jump to state change
	LDA is_select_down
	CMP #$01
	BEQ @selectState             ; jump to state change
	LDA is_paint_down
	CMP #$01
	BEQ @paintState              ; jump to state change
	LDA #$00
	JMP @save
@selectState:
  LDA #$01
  JMP @save
	LDA #$01
	JMP @save
@startState:
  LDA #$02
  JMP @save
	LDA #$02
	JMP @save
@paintState:
  LDA #$03
  JMP @save
	LDA #$03
	JMP @save
@save:
  STA $0202
  RTS

;;
	STA $0202
	RTS 

Paint:
  LDA PPU_STATUS
  LDA pos_y
  CLC
  ROL
  ROL
  PHA
  ROL
  AND #$03
  ORA #$20
  STA PPU_ADDRESS
  PLA
  AND #$E0
  STA temp_value               ; Store value in zero-page
  LDA pos_x
  LSR
  LSR
  LSR
  CLC
  ADC temp_value               ; Use value in zero-page
  STA PPU_ADDRESS
  JSR LoadTile
  STA PPU_DATA
  RTS

;;
	LDA PPU_STATUS
	LDA pos_y
	CLC 
	ROL 
	ROL 
	PHA 
	ROL 
	AND #$03
	ORA #$20
	STA PPU_ADDRESS
	PLA 
	AND #$E0
	STA temp_value               ; Store value in zero-page
	LDA pos_x
	LSR 
	LSR 
	LSR 
	CLC 
	ADC temp_value               ; Use value in zero-page
	STA PPU_ADDRESS
	JSR LoadTile
	STA PPU_DATA
	RTS 

PaintMirrorX:
  LDA PPU_STATUS
  LDA pos_y
  CLC
  ROL
  ROL
  PHA
  ROL
  AND #$03
  ORA #$20
  STA PPU_ADDRESS
  PLA
  AND #$E0
  STA temp_value               ; Store value in zero-page
  LDA #$80
  SEC                          ; mirror
  SBC pos_x
  CLC
  ADC #$80
  LSR
  LSR
  LSR
  CLC
  ADC temp_value               ; Use value in zero-page
  STA PPU_ADDRESS
  JSR LoadMirrorTileX
  CLC
  ADC #$02
  STA PPU_DATA
  RTS

;;
	LDA PPU_STATUS
	LDA pos_y
	CLC 
	ROL 
	ROL 
	PHA 
	ROL 
	AND #$03
	ORA #$20
	STA PPU_ADDRESS
	PLA 
	AND #$E0
	STA temp_value               ; Store value in zero-page
	LDA #$80
	SEC                          ; mirror
	SBC pos_x
	CLC 
	ADC #$80
	LSR 
	LSR 
	LSR 
	CLC 
	ADC temp_value               ; Use value in zero-page
	STA PPU_ADDRESS
	JSR LoadMirrorTileX
	CLC 
	ADC #$02
	STA PPU_DATA
	RTS 

PaintMirrorY:
  LDA PPU_STATUS
  LDA #$80
  SEC
  SBC pos_y
  CLC
  ADC #$80
  CLC
  ROL
  ROL
  PHA
  ROL
  AND #$03
  ORA #$20
  STA PPU_ADDRESS
  PLA
  AND #$E0
  STA temp_value               ; Store value in zero-page
  LDA pos_x
  LSR
  LSR
  LSR
  CLC
  ADC temp_value               ; Use value in zero-page
  STA PPU_ADDRESS
  JSR LoadMirrorTileY
  STA PPU_DATA
  RTS

;;
	LDA PPU_STATUS
	LDA #$80
	SEC 
	SBC pos_y
	CLC 
	ADC #$80
	CLC 
	ROL 
	ROL 
	PHA 
	ROL 
	AND #$03
	ORA #$20
	STA PPU_ADDRESS
	PLA 
	AND #$E0
	STA temp_value               ; Store value in zero-page
	LDA pos_x
	LSR 
	LSR 
	LSR 
	CLC 
	ADC temp_value               ; Use value in zero-page
	STA PPU_ADDRESS
	JSR LoadMirrorTileY
	STA PPU_DATA
	RTS 

PaintMirrorXY:
  LDA PPU_STATUS
  LDA #$80
  SEC
  SBC pos_y
  CLC
  ADC #$80
  CLC
  ROL
  ROL
  PHA
  ROL
  AND #$03
  ORA #$20
  STA PPU_ADDRESS
  PLA
  AND #$E0
  STA temp_value               ; Store value in zero-page
  LDA #$80
  SEC                          ; mirror
  SBC pos_x
  CLC
  ADC #$80
  LSR
  LSR
  LSR
  CLC
  ADC temp_value               ; Use value in zero-page
  STA PPU_ADDRESS
  JSR LoadMirrorTileXY
  STA PPU_DATA
  RTS

;;
	LDA PPU_STATUS
	LDA #$80
	SEC 
	SBC pos_y
	CLC 
	ADC #$80
	CLC 
	ROL 
	ROL 
	PHA 
	ROL 
	AND #$03
	ORA #$20
	STA PPU_ADDRESS
	PLA 
	AND #$E0
	STA temp_value               ; Store value in zero-page
	LDA #$80
	SEC                          ; mirror
	SBC pos_x
	CLC 
	ADC #$80
	LSR 
	LSR 
	LSR 
	CLC 
	ADC temp_value               ; Use value in zero-page
	STA PPU_ADDRESS
	JSR LoadMirrorTileXY
	STA PPU_DATA
	RTS 

LoadTile:
  ; (shape * 64) + (color * 16) + rotation
  LDX shape
  LDY shape_anchors, x
  STY $20
  ;
  LDX color_fg
  LDY foreground_anchors, x
  STY $21
  ;
  LDX color_bg
  LDY background_anchors, x
  STY $22
  ;
  LDA $20
  CLC
  ADC $21
  ADC $22
  ADC rotation
  RTS

;;
	; (shape * 64) + (color * 16) + rotation
	LDX shape
	LDY shape_anchors, x
	STY $20
	LDX color_fg
	LDY foreground_anchors, x
	STY $21
	LDX color_bg
	LDY background_anchors, x
	STY $22
	LDA $20
	CLC 
	ADC $21
	ADC $22
	ADC rotation
	RTS 

LoadMirrorTileX:
  ; (shape * 64) + (color * 16) + rotation
  LDX shape
  LDY shape_anchors, x
  STY $20
  ;
  LDX color_fg
  LDY foreground_anchors, x
  STY $21
  ;
  LDX color_bg
  LDY background_anchors, x
  STY $22
  ; mirror rotation
  LDX rotation
  LDY mirror_rotation_x, x
  STY $23
  ;
  LDA $20
  CLC
  ADC $21
  ADC $22
  ADC $23
  RTS

;;
	; (shape * 64) + (color * 16) + rotation
	LDX shape
	LDY shape_anchors, x
	STY $20
	LDX color_fg
	LDY foreground_anchors, x
	STY $21
	LDX color_bg
	LDY background_anchors, x
	STY $22
	; mirror rotation
	LDX rotation
	LDY mirror_rotation_x, x
	STY $23
	LDA $20
	CLC 
	ADC $21
	ADC $22
	ADC $23
	RTS 

LoadMirrorTileY:
  ; (shape * 64) + (color * 16) + rotation
  LDX shape
  LDY shape_anchors, x
  STY $20
  ;
  LDX color_fg
  LDY foreground_anchors, x
  STY $21
  ;
  LDX color_bg
  LDY background_anchors, x
  STY $22
  ; mirror rotation
  LDX rotation
  LDY mirror_rotation_y, x
  STY $23
  ;
  LDA $20
  CLC
  ADC $21
  ADC $22
  ADC $23
  RTS

;;
	; (shape * 64) + (color * 16) + rotation
	LDX shape
	LDY shape_anchors, x
	STY $20
	LDX color_fg
	LDY foreground_anchors, x
	STY $21
	LDX color_bg
	LDY background_anchors, x
	STY $22
	; mirror rotation
	LDX rotation
	LDY mirror_rotation_y, x
	STY $23
	LDA $20
	CLC 
	ADC $21
	ADC $22
	ADC $23
	RTS 

LoadMirrorTileXY:
  ; (shape * 64) + (color * 16) + rotation
  LDX shape
  LDY shape_anchors, x
  STY $20
  ;
  LDX color_fg
  LDY foreground_anchors, x
  STY $21
  ;
  LDX color_bg
  LDY background_anchors, x
  STY $22
  ; mirror rotation
  LDX rotation
  LDY mirror_rotation_xy, x
  STY $23
  ;
  LDA $20
  CLC
  ADC $21
  ADC $22
  ADC $23
  RTS
	; (shape * 64) + (color * 16) + rotation
	LDX shape
	LDY shape_anchors, x
	STY $20
	LDX color_fg
	LDY foreground_anchors, x
	STY $21
	LDX color_bg
	LDY background_anchors, x
	STY $22
	; mirror rotation
	LDX rotation
	LDY mirror_rotation_xy, x
	STY $23
	LDA $20
	CLC 
	ADC $21
	ADC $22
	ADC $23
	RTS 

;; Tables

 .org $E000
	.org $E000

;; tables

colors_sprites:                ; [skip]
  .db $CC,$CD,$CE,$CF
	.db $CC,$CD,$CE,$CF

colors_sprites_fg:             ; [skip]
  .db $DC,$DD,$DE,$DF
	.db $DC,$DD,$DE,$DF

colors_sprites_bg:             ; [skip]
  .db $EC,$ED,$EE,$EF
	.db $EC,$ED,$EE,$EF

colors_lb:                     ; [skip]
  .db $22,$22,$22,$22
	.db $22,$22,$22,$22

colors_hb:                     ; [skip]
  .db $81,$A1,$C1,$E1
	.db $81,$A1,$C1,$E1

;; tables

shapes_lb:                     ; [skip]
  .db $22,$22,$22,$22
	.db $22,$22,$22,$22

shapes_hb:                     ; [skip]
  .db $01,$21,$41,$61
	.db $01,$21,$41,$61

shapes_off:                    ; [skip]
  .db $C8,$C9,$CA,$CB
shapes_on:                     ; [skip]
  .db $D8,$D9,$DA,$DB
	.db $C8,$C9,$CA,$CB

;;
shapes_on:                     ; [skip]
	.db $D8,$D9,$DA,$DB

tools_lb:                      ; [skip]
  .db $21,$21,$21,$21,$21,$21,$21,$21
  .db $22,$22,$22,$22,$22,$22,$22,$22
tools_hb:                      ; [skip]
  .db $01,$21,$41,$61,$81,$A1,$C1,$E1
  .db $01,$21,$41,$61,$81,$A1,$C1,$E1
	.db $21,$21,$21,$21,$21,$21,$21,$21
	.db $22,$22,$22,$22,$22,$22,$22,$22

;;
tools_hb:                      ; [skip]
	.db $01,$21,$41,$61,$81,$A1,$C1,$E1
	.db $01,$21,$41,$61,$81,$A1,$C1,$E1

families:                      ; [skip]
  .db $00,$00,$00,$00,$01,$01,$01,$01
  .db $02,$02,$02,$ff,$03,$03,$03,$03

;;
	.db $00,$00,$00,$00,$01,$01,$01,$01
	.db $02,$02,$02,$ff,$03,$03,$03,$03

shape_anchors:                 ; [skip]
  .db $00,$40,$80
	.db $00,$40,$80

foreground_anchors:            ; [skip]
  .db $00,$10,$20,$30
background_anchors:            ; [skip]
  .db $00,$04,$08,$0C
	.db $00,$10,$20,$30

;;
background_anchors:            ; [skip]
	.db $00,$04,$08,$0C

mirrors_lb:                    ; [skip]
  .db $21,$21,$21
	.db $21,$21,$21

mirrors_hb:                    ; [skip]
  .db $81,$A1,$C1
	.db $81,$A1,$C1

mirrors_x_toggle:              ; [skip]
  .db $C4,$D4
	.db $C4,$D4

mirrors_y_toggle:              ; [skip]
  .db $C5,$D5
	.db $C5,$D5

mirrors_xy_toggle:             ; [skip]
  .db $C6,$D6
	.db $C6,$D6

mirror_rotation_x:             ; [skip]
  .db $ff,$fe,$01,$00
	.db $ff,$fe,$01,$00

mirror_rotation_y:             ; [skip]
  .db $03,$02,$01,$00
mirror_rotation_xy:            ; [skip]
  .db $02,$03,$00,$01
	.db $03,$02,$01,$00

;;
mirror_rotation_xy:            ; [skip]
	.db $02,$03,$00,$01

palettes:                      ; [skip]
  .db $0F,$28,$3B,$30,$30,$10,$1D,$10,$1D,$10,$1D,$10,$1D,$10,$1D,$35
  .db $0F,$28,$3B,$30,$30,$10,$1D,$10,$1D,$10,$1D,$10,$1D,$10,$1D,$35
	.db $0F,$28,$3B,$30,$30,$10,$1D,$10,$1D,$10,$1D,$10,$1D,$10,$1D,$35
	.db $0F,$28,$3B,$30,$30,$10,$1D,$10,$1D,$10,$1D,$10,$1D,$10,$1D,$35

;; Vectors

  .pad $FFFA
  .dw NMI
  .dw RESET
  .dw 0
	.pad $FFFA
	.dw NMI
	.dw RESET
	.dw 0

;; Sprites

  .incbin "src/sprite.chr"
	.incbin "src/sprite.chr"

M tools/asm6 => tools/asm6 +0 -0
M tools/lin6 => tools/lin6 +0 -0
M tools/lin6.c => tools/lin6.c +434 -17
@@ 1,33 1,450 @@
/* usage: lin6 file.asm */
/* format: clang-format -i lin6.c */
/* Lin6
  Version 1.1
  https://wiki.xxiivv.com/lin6
*/

/* Rules:
  Variable names must always be lowercase.
  Variable length are always padded to col25.
  Variable comments are always padded to col32.
  Constant names must always be uppercase.
  Constant length are always padded to col21.
  Constant comments are always padded to col32.
  Label names must always be capitalized.
  Label names aalways end with :.
  Label names are always preceeded with a linebreak.
  Label comments are always padded to col32.
  Directive names are always padded to col2.
  Directive comments are always padded to col32.
  Opcode names are always uppercase.
  Opcode names are always padded to col2.
  Opcode comments are always padded to col32.
  Inline comments are always padded to col2.
  Spacing comments are always preceeded and followed by with a linebreak.
*/

#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
  /* Read argv */
  if (argc != 2) {
    printf("Error: Missing file argument.\n");
    exit(1);
#define VERSION "1.1"
#define BUFLEN 512

char *OPCODES[] = {"ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI",
                   "BNE", "BPL", "BRK", "BVC", "BVS", "CLC", "CLD", "CLI",
                   "CLV", "CMP", "CPX", "CPY", "DEC", "DEX", "DEY", "EOR",
                   "INC", "INX", "INY", "JMP", "JSR", "LDA", "LDX", "LDY",
                   "LSR", "NOP", "ORA", "PHA", "PHP", "PLA", "PLP", "ROL",
                   "ROR", "RTI", "RTS", "SBC", "SEC", "SED", "SEI", "STA",
                   "STX", "STY", "TAX", "TAY", "TSX", "TXA", "TXS", "TYA"};

void substr(char *src, char *dest, int from, int to) {
  memcpy(dest, src + from, to);
  dest[to] = '\0';
}

void trimstr(char *src, char *dest) {
  /* leading whitespace */
  int a, b, len = strlen(src) + 1;
  for (a = 0; a < len; a++) {
    if (src[a] != ' ' && src[a] != '\t') {
      break;
    }
  }
  /* traling whitespace */
  for (b = 2; b < len; b++) {
    if (src[len - b] != ' ' && src[len - b] != '\n') {
      break;
    }
  }
  substr(src, dest, a, len - b - a + 1);
}

  /* Open file */
  FILE *filePointer;
  if ((filePointer = fopen(argv[1], "r")) == NULL) {
    printf("Error: Cannot open file.\n");
    exit(1);
void fillstr(char *src, int limit, char c) {
  int i, len = strlen(src);
  for (i = len; i < limit; i++) {
    src[i] = c;
  }
  src[limit] = '\0';
}

  int bufferLength = 255;
  char buffer[bufferLength];
void swapcstr(char *src, char a, char b) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    src[i] = src[i] == a ? b : src[i];
  }
}

  while (fgets(buffer, bufferLength, filePointer)) {
    printf("%s", buffer);
void cpystr(char *src, char *dest) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    dest[i] = src[i];
  }
  dest[len] = '\0';
}

  fclose(filePointer);
void ucstr(char *src) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    src[i] = toupper(src[i]);
  }
}

void lcstr(char *src) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    src[i] = tolower(src[i]);
  }
}

int index_of_comment(char *src) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    if (src[i] == ';') {
      return i;
    }
  }
  return -1;
}

bool opcode_exists(char *opcode) {
  int i;
  for (i = 0; i < 56; i++) {
    if (strcmp(OPCODES[i], opcode) == 0) {
      return true;
    }
  }
  return false;
}

bool file_exists(char *filename) {
  FILE *file = fopen(filename, "r");
  if (file != NULL) {
    fclose(file);
    return true;
  }
  return false;
}

void set_line_key(char *src, char *dest) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    if (src[i] == ' ') {
      break;
    }
  }
  substr(src, dest, 0, i);
  trimstr(dest, dest);
}

void set_line_value(char *src, char *dest) {
  bool step = false;
  int i, comment = index_of_comment(src), len = strlen(src);
  if (comment > -1) {
    len = comment;
  }
  for (i = 0; i < len; i++) {
    if (src[i] != ' ' && step == true) {
      break;
    } else if (src[i] == ' ') {
      step = true;
    }
  }
  substr(src, dest, i, len - i);
  trimstr(dest, dest);
}

void set_line_comment(char *src, char *dest) {
  int i, len = strlen(src);
  for (i = 0; i < len; i++) {
    if (src[i] == ';') {
      break;
    }
  }
  substr(src, dest, i, len - i);
  trimstr(dest, dest);
}

bool has_comment(char *line) {
  int i, len = strlen(line);
  for (i = 0; i < len; i++) {
    if (line[i] == ';') {
      return true;
    }
  }
  return false;
}

bool is_capital(char *str) { return str[0] == toupper(str[0]); }

bool is_lower(char *str) {
  int i, len = strlen(str);
  for (i = 0; i < len; i++) {
    if (str[i] != tolower(str[i])) {
      return false;
    }
  }
  return true;
}

bool is_upper(char *str) {
  int i, len = strlen(str);
  for (i = 0; i < len; i++) {
    if (str[i] != toupper(str[i])) {
      return false;
    }
  }
  return true;
}

bool is_label(char *line) {
  int i, len = strlen(line);
  for (i = 0; i < len; i++) {
    if (line[i] == ' ' || line[i] == ';') {
      return false;
    }
    if (line[i] == ':') {
      return true;
    }
  }
  return false;
}

bool is_opcode(char *line) {
  char opcode[4];
  substr(line, opcode, 0, 3);
  ucstr(opcode);
  return opcode_exists(opcode);
}

bool is_directive(char *line) { return line[0] == '.'; }

bool is_spacer_comment(char *line) { return line[0] == ';' && line[1] == ';'; }

bool is_inline_comment(char *line) { return line[0] == ';' && line[1] == ' '; }

bool is_variable(char *line) {
  int i, len = strlen(line);
  for (i = 0; i < len; i++) {
    if (line[i] == '.' && line[i + 1] == 'd' && line[i + 2] == 's' &&
        line[i + 3] == 'b') {
      return true;
    }
  }
  return false;
}

bool is_constant(char *line) {
  int i, len = strlen(line);
  for (i = 0; i < len; i++) {
    if (line[i] == '.' && line[i + 1] == 'e' && line[i + 2] == 'q' &&
        line[i + 3] == 'u') {
      return true;
    }
  }
  return false;
}

void save(char *filepath, char *output, bool do_inplace) {
  if (do_inplace) {
    FILE *fw = fopen(filepath, "w");
    fprintf(fw, "%s", output);
    fclose(fw);
  } else {
    printf("%s\n", output);
  }
}

void lint(char *filepath, char *output, bool do_debug) {
  FILE *fr = fopen(filepath, "r");
  int id = 0, len, count = 0, opcodes = 0, directives = 0, spacers = 0,
      labels = 0, variables = 0, constants = 0, comments = 0;
  bool skipped_last = false;
  char line[BUFLEN];
  char trimed[BUFLEN];
  while (fgets(line, BUFLEN, fr)) {
    id++;
    trimstr(line, trimed);
    swapcstr(trimed, '\t', ' ');
    len = strlen(trimed);
    if (len < 3) {
      continue;
    }
    if (is_label(trimed)) {
      char key[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_comment(trimed, comment);
      if (!is_capital(key)) {
        printf("Lin6: Label %s not capitalized, at %s:%d.\n", key, filepath,
               id);
      }
      if (key[0] != '@' && !skipped_last) {
        strcat(output, "\n");
      }
      if (has_comment(line)) {
        fillstr(key, 30, ' ');
        strcat(output, key);
        strcat(output, " ");
        strcat(output, comment);
      } else {
        strcat(output, key);
      }
      skipped_last = false;
      labels++;
    } else if (is_opcode(trimed)) {
      char key[BUFLEN];
      char value[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_value(trimed, value);
      set_line_comment(trimed, comment);
      ucstr(key);
      strcat(output, "\t");
      strcat(output, key);
      strcat(output, " ");
      if (has_comment(line)) {
        fillstr(value, 24, ' ');
        strcat(output, value);
        strcat(output, " ");
        strcat(output, comment);
      } else {
        strcat(output, value);
      }
      skipped_last = false;
      opcodes++;
    } else if (is_directive(trimed)) {
      char key[BUFLEN];
      char value[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_value(trimed, value);
      set_line_comment(trimed, comment);
      strcat(output, "\t");
      strcat(output, key);
      strcat(output, " ");
      if (has_comment(line)) {
        strcat(output, value);
        strcat(output, " ");
        strcat(output, comment);
      } else {
        strcat(output, value);
      }
      skipped_last = false;
      directives++;
    } else if (is_inline_comment(trimed)) {
      strcat(output, "\t");
      strcat(output, trimed);
      skipped_last = false;
      comments++;
    } else if (is_spacer_comment(trimed)) {
      strcat(output, "\n");
      strcat(output, trimed);
      strcat(output, "\n");
      skipped_last = true;
      spacers++;
    } else if (is_variable(trimed)) {
      char key[BUFLEN];
      char value[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_value(trimed, value);
      set_line_comment(trimed, comment);
      fillstr(key, 24, ' ');
      if (!is_lower(key)) {
        printf("Lin6: Variable %s not lowercase, at %s:%d.\n", key, filepath,
               id);
      }
      strcat(output, key);
      strcat(output, value);
      strcat(output, " ");
      strcat(output, comment);
      skipped_last = false;
      variables++;
    } else if (is_constant(trimed)) {
      char key[BUFLEN];
      char value[BUFLEN];
      char comment[BUFLEN];
      set_line_key(trimed, key);
      set_line_value(trimed, value);
      set_line_comment(trimed, comment);
      fillstr(key, 20, ' ');
      if (!is_upper(key)) {
        printf("Lin6: Constant %s not uppercase, at %s:%d.\n", key, filepath,
               id);
      }
      strcat(output, key);
      strcat(output, value);
      strcat(output, " ");
      strcat(output, comment);
      skipped_last = false;
      constants++;
    } else {
      printf("Lin6: Unknown line-type, at %s:%d.\n", filepath, id);
      strcat(output, line);
    }
    strcat(output, "\n");
    count++;
  }
  if (do_debug) {
    printf("Lin6 debug: %d lines: \n", count);
    printf("\t%d labels\n", labels);
    printf("\t%d opcodes\n", opcodes);
    printf("\t%d directives\n", directives);
    printf("\t%d spacers\n", spacers);
    printf("\t%d variables\n", variables);
    printf("\t%d constants\n", constants);
  }
  fclose(fr);
}

void show_version() { puts("Lin6 " VERSION); }

void show_help() {
  show_version();
  puts("");
  puts("A tool to format 6502 assembly code.\n");
  puts("Usage:  Lin6 [-options] [<file> ...]\n");
  puts("\t-?\tShow this help");
  puts("\t-v\tShow version");
  puts("\t-i\tInplace edit files");
  puts("\t-d\tShow debug\n");
  puts("See README for more info.\n");
}

int main(int argc, char *argv[]) {
  bool do_debug = false, do_inplace = false;
  int i;
  if (argc < 2) {
    show_help();
    exit(0);
  }
  for (i = 1; i < argc; i++) {
    if (argv[i][1] == '?') {
      show_help();
      exit(0);
    } else if (argv[i][1] == 'v') {
      show_version();
      exit(0);
    }
    /* read flags */
    if (argv[i][1] == 'd') {
      do_debug = true;
    } else if (argv[i][1] == 'i') {
      do_inplace = true;
    }
    /* do files */
    else {
      char linted[51200];
      if (!file_exists(argv[i])) {
        printf("Lin6: File %s not found\n", argv[i]);
        exit(0);
      }
      lint(argv[i], linted, do_debug);
      save(argv[i], linted, do_inplace);
    }
  }
  return 0;
}