From 8e20553b524d8ef0ce45bdff3af44303d61ce139 Mon Sep 17 00:00:00 2001 From: John Bintz <27256+johnbintz@users.noreply.github.com> Date: Wed, 14 Jul 2021 21:48:06 -0400 Subject: [PATCH] initial commit --- LICENSE | 19 +++ README.md | 20 +++ calderon_with_bamboo.chr | Bin 0 -> 8192 bytes data.asm | 70 +++++++++++ galois16.asm | 37 ++++++ nesdefs.dasm | 184 +++++++++++++++++++++++++++ nesppu.dasm | 64 ++++++++++ password.asm | 265 +++++++++++++++++++++++++++++++++++++++ password.nes | Bin 0 -> 49168 bytes processing.asm | 118 +++++++++++++++++ view.asm | 115 +++++++++++++++++ 11 files changed, 892 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 calderon_with_bamboo.chr create mode 100644 data.asm create mode 100644 galois16.asm create mode 100644 nesdefs.dasm create mode 100644 nesppu.dasm create mode 100644 password.asm create mode 100644 password.nes create mode 100644 processing.asm create mode 100644 view.asm diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a7b3028 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright © 2021 John Bintz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e90fe9e --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# NES Password ROM + +An NES ROM that shows how the Space character worked in Metroid passwords. +It was also an opportunity to learn 6502 assembler and the basics of +NES development! Visit +[r1kr.blog/read/nestextpasswordrom](https://r1kr.blog/read/nestextpasswordrom) +to learn more! + +## Building + +* Install [dasm](https://dasm-assembler.github.io/) +* Run `dasm password.asm -f3 -opassword.nes` +* Run `password.nes` in your favorite emulator. + +## Credits + +* 8bitworkshop: https://8bitworkshop.com/ +* galois16: https://github.com/bbbradsmith/prng_6502/blob/master/galois16.s +* Calderon Handwriting Condensed: https://www.romhacking.net/fonts/32/ + diff --git a/calderon_with_bamboo.chr b/calderon_with_bamboo.chr new file mode 100644 index 0000000000000000000000000000000000000000..765a6940213fb7d6d3afc5bcc16313a510070633 GIT binary patch literal 8192 zcmeH|zi-n(6vv;Fs(3)uV+X2w?3l4gsbhW&9W!)diiCt1J$7*8OWNVa6B9@bU6A?* zFfb(G5{WV(@dq%}8w*mr)u>jE_s)KGDQgFU?j`Ey&hPHsz3+QY0jyaC8U+Ly;HPHe zS(;H^-;{|{Q9lyUT5&x;Y#2$h2$*3SqGiC0B&{#df5nb#*1LvYr>_dn17;yaPJZTg zC~}I=ETre70Ta)qFP(3=FXg`Ly1=Y2erv!(JRf{I&+uko-|zPeDS??Ol}niev$Knh zvD*P=rcgeWp}%4-M1~T;pA_|6kPdGo?q}jIJWc{LBl%H(X6&J<0WhUHPLCC?uW9-e z&to2aeRqTgOew#dyfX1CV%}&?y(u}|3rzB#s28Rmi1eJ!V@6U$uGwF)plVV*Ynt%r zzs7za{Wv!qkDu)AZEXQlqO(u;W75E^uUJsqi2|56>vj9{NGZ%4pdywN=Nal-5n!MM z)`RJJ)jB3U&qZYX#LjL=rMx1=&8eU@tGKF;ujp09a)^c=M5Y013SkP5i$+C&8S z`4a6K>E0NZI@oZ47~q_V&okP{jFgHz*W_=QsVXir5|NkT{)*VEm|h4>06K>BW17O_ zoH-t21K^yA1@BApGjR|Kv)(Yr2B4oZCJt}koF-)V`P1|16zA*R?r_*V=MnlZ|MCm= zE*l-kX}8*r$)Eo0>>Qx&Y%FT4@(G^RdEK8(KA=tBC$(X3q+x0r4@A9anNVfk+}Ih7 z(s1DG#cGbXB^>o^;{H8gAASjnTUA(`$c6Vj9MTxZWJqW|cDR}Nmuh;GA`Nbs+ zweI4-U_BbH&*t-atM#c3_={AQ-BQobKLU7p#~*~WKds*b8ZV38?Ekh%qx*LN%A~i} z#w0W)GCCXs6@D1U!fD^z&H$1{nWyOb-}--TGYFDIYgpab2zp6!h^aegTF3E$UOYI{ zSzZ)nxmZ9NMKnSQtXT!D0#*U5fK|XMU=^?mSOu&CRspMkRlq7>6|f5Y!wUQcD<~~L literal 0 HcmV?d00001 diff --git a/data.asm b/data.asm new file mode 100644 index 0000000..1040b2a --- /dev/null +++ b/data.asm @@ -0,0 +1,70 @@ +Letters + byte "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?! " +Passwords + byte "D N E " + byte "! ! ! " + byte "T R O " + byte "H R O " + + byte "JUSTIN" + byte "BAILEY" + byte "!a!b!c" + byte "!d!e!f" + + byte "6eW3!!" + byte "!!!!00" + byte "F38W!H" + byte "C0042N" + + byte "llv!?l" + byte "z!!mU4" + byte "!!y000" + byte "00m03x" + + byte "DRAGON" + byte "BALL Z" + byte "Dragon" + byte "Ball z" + +CanAlsoMessage + byte " might also be written as " + byte 0 + +AButtonMessage + byte "A - Change password" + byte 0 + +BButtonMessage + byte "B - Randomize alternative" + byte 0 + +R1KRTitleOne: + byte "The Rabbit with" + byte 0 +R1KRTitleTwo: + byte "1000 Repos" + byte 0 + +R1KRBlogTitle: + byte "NES Text Passwords" + byte 0 + +R1KRBlogLinkOne: + byte "r1kr.blog/read/" + byte 0 + +R1KRBlogLinkTwo: + byte "nestextpasswordrom" + byte 0 + +Palette: + hex ff ;screen color + hex 0d262400 ;background 0 + hex 0d262100 ;background 1 + hex 0d262a00 ;background 2 + hex 00122200 ;background 3 + hex 19293900 ;sprite 0 + hex 1a2a3a00 ;sprite 1 + hex 1b2b3b00 ;sprite 2 + hex 1c2c3c ;sprite 3 + diff --git a/galois16.asm b/galois16.asm new file mode 100644 index 0000000..7b49fed --- /dev/null +++ b/galois16.asm @@ -0,0 +1,37 @@ +; +; 6502 LFSR PRNG - 16-bit +; Brad Smith, 2019 +; http://rainwarrior.ca +; + +; A 16-bit Galois LFSR + +; Possible feedback values that generate a full 65535 step sequence: +; $2D = %00101101 +; $39 = %00111001 +; $3F = %00111111 +; $53 = %01010011 +; $BD = %10111101 +; $D7 = %11010111 + +; $39 is chosen for its compact bit pattern + +; simplest version iterates the LFSR 8 times to generate 8 random bits +; 133-141 cycles per call +; 19 bytes + +galois16: subroutine + ldy #8 + lda Seed+0 +.one + asl ; shift the register + rol Seed+1 + bcc .two + eor #$39 ; apply XOR feedback whenever a 1 bit is shifted out +.two + dey + bne .one + sta Seed+0 + cmp #0 ; reload flags + rts + diff --git a/nesdefs.dasm b/nesdefs.dasm new file mode 100644 index 0000000..554d5a8 --- /dev/null +++ b/nesdefs.dasm @@ -0,0 +1,184 @@ + + processor 6502 + +;;;;; CONSTANTS + +PPU_CTRL = $2000 +PPU_MASK = $2001 +PPU_STATUS = $2002 +OAM_ADDR = $2003 +OAM_DATA = $2004 +PPU_SCROLL = $2005 +PPU_ADDR = $2006 +PPU_DATA = $2007 + +PPU_OAM_DMA = $4014 +DMC_FREQ = $4010 +APU_STATUS = $4015 +APU_NOISE_VOL = $400C +APU_NOISE_FREQ = $400E +APU_NOISE_TIMER = $400F +APU_DMC_CTRL = $4010 +APU_CHAN_CTRL = $4015 +APU_FRAME = $4017 + +JOYPAD1 = $4016 +JOYPAD2 = $4017 + +; NOTE: I've put this outside of the PPU & APU, because it is a feature +; of the APU that is primarily of use to the PPU. +OAM_DMA = $4014 +; OAM local RAM copy goes from $0200-$02FF: +OAM_RAM = $0200 + +; PPU_CTRL flags +CTRL_NMI = %10000000 ; Execute Non-Maskable Interrupt on VBlank +CTRL_8x8 = %00000000 ; Use 8x8 Sprites +CTRL_8x16 = %00100000 ; Use 8x16 Sprites +CTRL_BG_0000 = %00000000 ; Background Pattern Table at $0000 in VRAM +CTRL_BG_1000 = %00010000 ; Background Pattern Table at $1000 in VRAM +CTRL_SPR_0000 = %00000000 ; Sprite Pattern Table at $0000 in VRAM +CTRL_SPR_1000 = %00001000 ; Sprite Pattern Table at $1000 in VRAM +CTRL_INC_1 = %00000000 ; Increment PPU Address by 1 (Horizontal rendering) +CTRL_INC_32 = %00000100 ; Increment PPU Address by 32 (Vertical rendering) +CTRL_NT_2000 = %00000000 ; Name Table Address at $2000 +CTRL_NT_2400 = %00000001 ; Name Table Address at $2400 +CTRL_NT_2800 = %00000010 ; Name Table Address at $2800 +CTRL_NT_2C00 = %00000011 ; Name Table Address at $2C00 + +; PPU_MASK flags +MASK_TINT_RED = %00100000 ; Red Background +MASK_TINT_BLUE = %01000000 ; Blue Background +MASK_TINT_GREEN = %10000000 ; Green Background +MASK_SPR = %00010000 ; Sprites Visible +MASK_BG = %00001000 ; Backgrounds Visible +MASK_SPR_CLIP = %00000100 ; Sprites clipped on left column +MASK_BG_CLIP = %00000010 ; Background clipped on left column +MASK_COLOR = %00000000 ; Display in Color +MASK_MONO = %00000001 ; Display in Monochrome + +; read flags +F_BLANK = %10000000 ; VBlank Active +F_SPRITE0 = %01000000 ; VBlank hit Sprite 0 +F_SCAN8 = %00100000 ; More than 8 sprites on current scanline +F_WIGNORE = %00010000 ; VRAM Writes currently ignored. + + +;;;;; CARTRIDGE FILE HEADER + +NES_MIRR_HORIZ = 0 +NES_MIRR_VERT = 1 +NES_MIRR_QUAD = 8 + + MAC NES_HEADER + seg Header + org $7ff0 +.NES_MAPPER SET {1} ;mapper number +.NES_PRG_BANKS SET {2} ;number of 16K PRG banks, change to 2 for NROM256 +.NES_CHR_BANKS SET {3} ;number of 8K CHR banks (0 = RAM) +.NES_MIRRORING SET {4} ;0 horizontal, 1 vertical, 8 four screen + byte $4e,$45,$53,$1a ; header + byte .NES_PRG_BANKS + byte .NES_CHR_BANKS + byte .NES_MIRRORING|(.NES_MAPPER<<4) + byte .NES_MAPPER&$f0 + byte 0,0,0,0,0,0,0,0 ; reserved, set to zero + seg Code + org $8000 + ENDM + +;;;;; NES_INIT SETUP MACRO (place at start) + + MAC NES_INIT + sei ;disable IRQs + cld ;decimal mode not supported + ldx #$ff + txs ;set up stack pointer + inx ;increment X to 0 + stx PPU_MASK ;disable rendering + stx DMC_FREQ ;disable DMC interrupts + stx PPU_CTRL ;disable NMI interrupts + bit PPU_STATUS ;clear VBL flag + bit APU_CHAN_CTRL ;ack DMC IRQ bit 7 + lda #$40 + sta APU_FRAME ;disable APU Frame IRQ + lda #$0F + sta APU_CHAN_CTRL ;disable DMC, enable/init other channels. + ENDM + +;;;;; NES_VECTORS - CPU vectors at end of address space + + MAC NES_VECTORS + seg Vectors ; segment "Vectors" + org $fffa ; start at address $fffa + .word NMIHandler ; $fffa vblank nmi + .word Start ; $fffc reset + .word NMIHandler ; $fffe irq / brk + ENDM + +;;;;; PPU_SETADDR
- set 16-bit PPU address + + MAC PPU_SETADDR + lda #>{1} ; upper byte + sta PPU_ADDR + lda #<{1} ; lower byte + sta PPU_ADDR + ENDM + +;;;;; PPU_SETVALUE - feed 8-bit value to PPU + + MAC PPU_SETVALUE + lda #{1} + sta PPU_DATA + ENDM + +;;;;; SAVE_REGS - save A/X/Y registers + + MAC SAVE_REGS + pha + txa + pha + tya + pha + ENDM + +;;;;; RESTORE_REGS - restore Y/X/A registers + + MAC RESTORE_REGS + pla + tay + pla + tax + pla + ENDM + +;------------------------------------------------------------------------------- +; SLEEP clockcycles +; Original author: Thomas Jentzsch +; Inserts code which takes the specified number of cycles to execute. This is +; useful for code where precise timing is required. +; LEGAL OPCODE VERSION MAY AFFECT FLAGS (uses 'bit' opcode) + +NO_ILLEGAL_OPCODES = 1 + + MAC SLEEP ;usage: SLEEP n (n>1) +.CYCLES SET {1} + + IF .CYCLES < 2 + ECHO "MACRO ERROR: 'SLEEP': Duration must be > 1" + ERR + ENDIF + + IF .CYCLES & 1 + IFNCONST NO_ILLEGAL_OPCODES + nop 0 + ELSE + bit $00 + ENDIF +.CYCLES SET .CYCLES - 3 + ENDIF + + REPEAT .CYCLES / 2 + nop + REPEND + ENDM diff --git a/nesppu.dasm b/nesppu.dasm new file mode 100644 index 0000000..a570dc7 --- /dev/null +++ b/nesppu.dasm @@ -0,0 +1,64 @@ + +;;;;; SUBROUTINES + +ClearRAM: subroutine + lda #0 ; A = 0 + tax ; X = 0 +.clearRAM + sta $0,x ; clear $0-$ff + cpx #$fe ; last 2 bytes of stack? + bcs .skipStack ; don't clear it + sta $100,x ; clear $100-$1fd +.skipStack + sta $200,x ; clear $200-$2ff + sta $300,x ; clear $300-$3ff + sta $400,x ; clear $400-$4ff + sta $500,x ; clear $500-$5ff + sta $600,x ; clear $600-$6ff + sta $700,x ; clear $700-$7ff + inx ; X = X + 1 + bne .clearRAM ; loop 256 times + rts + +; wait for VSYNC to start +WaitSync: + bit PPU_STATUS + bpl WaitSync + rts + +;;;;; RANDOM NUMBERS + +NextRandom subroutine + lsr + bcc .NoEor + eor #$d4 +.NoEor: + rts +; Get previous random value +PrevRandom subroutine + asl + bcc .NoEor + eor #$a9 +.NoEor: + rts + +;;;;; CONTROLLER READING + +ReadJoypad0 subroutine + ldy #0 +ReadJoypadY + lda #$01 + sta JOYPAD1,y ; set strobe bit + lsr ; now A is 0 + sta JOYPAD1,y ; clear strobe bit + ldx #8 ; read 8 bits +.loop: + pha ; save A (result) + lda JOYPAD1,y ; load controller state + lsr ; bit 0 -> carry + pla ; restore A (result) + rol ; carry -> bit 0 of result + dex ; X = X - 1 + bne .loop ; repeat if X is 0 + rts ; controller bits returned in A + diff --git a/password.asm b/password.asm new file mode 100644 index 0000000..61b0a5f --- /dev/null +++ b/password.asm @@ -0,0 +1,265 @@ +PASSWORD_LENGTH = 24 ; length for comparing to Y +SPACE_VALUE = 64 +SPACE_LOCATION = 64 +SPACE_BECOME_VALUE = SPACE_LOCATION - 1 +PASSWORD_COUNT = 5 + + include "nesdefs.dasm" + +;;;;; VARIABLES + + seg.u ZEROPAGE + org $0 + +;; global + +Seed ds 2 +PasswordInUse ds 1 + +;; controller + +CurrentController ds 1 +BButtonDown ds 1 +AButtonDown ds 1 + +;; decoding + +CurrentLetter ds 1 +CurrentPasswordChunk ds 1 +RandomValueForPasswordLetterModification ds 1 +DecodedPasswordOffset ds 1 +CurrentLetterPositionProcess ds 1 +CurrentLetterPositionDisplay ds 1 + +;; rendering + +BambooRenderPos ds 1 +StringToWrite ds 2 + + seg.u PASSWORDS + org $400 + +;; password storage + +DecodedPassword ds 24 +AlternatePassword ds 24 + + +;;;;; NES CARTRIDGE HEADER + + NES_HEADER 0,2,1,NES_MIRR_HORIZ ; mapper 0, 2 PRGs, 1 CHR + +;;;;; START OF CODE + +Start: +; wait for PPU warmup; clear CPU RAM + NES_INIT ; set up stack pointer, turn off PPU + jsr WaitSync ; wait for VSYNC + jsr ClearRAM ; clear RAM + jsr WaitSync ; wait for VSYNC (and PPU warmup) +; set palette and nametable VRAM + jsr SetPalette ; set palette colors + lda #0 + sta PasswordInUse + +; reset PPU address and scroll registers + lda #0 + sta PPU_ADDR + sta PPU_ADDR ; PPU addr = $0000 + sta PPU_SCROLL + sta PPU_SCROLL ; PPU scroll = $0000 + +; seed rng + lda #25 + sta Seed + lda #43 + sta Seed+1 + +; disable NMI and scroll + lda #0 ; disable NMI + sta PPU_CTRL + + lda #$0 + sta PPU_SCROLL + sta PPU_SCROLL ; PPU scroll = $0000 + +; set up controller + lda #0 + sta AButtonDown + sta BButtonDown + + jsr DecodePassword + jsr ConvertDecodedPasswordToAlternatePassword + + jsr WriteUI + +.endless + jsr ReadJoypad0 + sta CurrentController + + jsr HandleBButton + jsr HandleAButton + + inc Seed + jmp .endless ; endless loop + + +;;;;; COMMON SUBROUTINES + + include "nesppu.dasm" + include "processing.asm" + include "view.asm" + include "galois16.asm" + include "data.asm" + +WriteString: subroutine + ldy #0 +.Continue: + lda (StringToWrite),y + beq .Done + sta PPU_DATA + iny + bne .Continue +.Done + rts + + MAC WRITE_STRING + lda #<{1} + sta StringToWrite + lda #>{1} + sta StringToWrite+1 + jsr WriteString + ENDM + +WriteUI: subroutine + lda #0 + sta PPU_MASK ; turn rendering off + + PPU_SETADDR $2069 + jsr WritePassword + + PPU_SETADDR $20c1 + WRITE_STRING CanAlsoMessage + + PPU_SETADDR $2109 + jsr WriteAlternateDecodedPassword + + PPU_SETADDR $21a6 + WRITE_STRING AButtonMessage + + PPU_SETADDR $21c3 + WRITE_STRING BButtonMessage + + PPU_SETADDR $2242 + jsr DrawBamboo + + PPU_SETADDR $224a + WRITE_STRING R1KRTitleOne + + PPU_SETADDR $226d + WRITE_STRING R1KRTitleTwo + + PPU_SETADDR $22a9 + WRITE_STRING R1KRBlogTitle + + PPU_SETADDR $22e9 + WRITE_STRING R1KRBlogLinkOne + + PPU_SETADDR $230b + WRITE_STRING R1KRBlogLinkTwo + +;;;;; palette stuff + + PPU_SETADDR $23e8 + + lda #%01010101 + sta PPU_DATA + sta PPU_DATA + + PPU_SETADDR $23f0 + + lda #%10101010 + sta PPU_DATA + sta PPU_DATA + + lda #MASK_BG + sta PPU_MASK ; enable rendering + lda #$0 + sta PPU_ADDR + sta PPU_ADDR ; PPU addr = $0000 + + rts + +HandleBButton: subroutine + lda #%01000000 ; lowest bit is b + bit CurrentController + beq .BButtonUp ; button is up +.BButtonDown + lda #0 + cmp BButtonDown + bne .BButtonWasDown + + jsr ConvertDecodedPasswordToAlternatePassword + jsr WriteUI + +.BButtonWasDown + lda #1 + sta BButtonDown + bne .BButtonDone +.BButtonUp + lda #0 + sta BButtonDown +.BButtonDone + rts + +HandleAButton: subroutine + lda #%10000000 ; lowest bit is b + bit CurrentController + beq .AButtonUp ; button is up +.AButtonDown + lda #0 + cmp AButtonDown + bne .AButtonWasDown + + inc PasswordInUse + lda #PASSWORD_COUNT + cmp PasswordInUse + bne .NotEqual + + lda #0 + sta PasswordInUse +.NotEqual + + jsr DecodePassword + jsr ConvertDecodedPasswordToAlternatePassword + jsr WriteUI + +.AButtonWasDown + lda #1 + sta AButtonDown + bne .AButtonDone +.AButtonUp + lda #0 + sta AButtonDown +.AButtonDone + rts + + +;;;;; INTERRUPT HANDLERS + + +NMIHandler: subroutine + SAVE_REGS + + RESTORE_REGS + rti + +;;;;; CPU VECTORS + + NES_VECTORS + + org $10000 + + + incbin "calderon_with_bamboo.chr" + incbin "calderon_with_bamboo.chr" diff --git a/password.nes b/password.nes new file mode 100644 index 0000000000000000000000000000000000000000..738fa2e0b5cfbeafa4d503db2e5dbc32924b0e6b GIT binary patch literal 49168 zcmeI2ZERE58OI;L#BpcAy=iKW< zLiS}J(4Yb6XA_f6D2g2R88eWclaCOAc|#r(Ghk!zp%b&zC+9eE!aYt^SF#c5uStZe&k5TdH9c! zHGDN96J$89JfR)o@1!?$om27cxAR{`44S0BI?bw%x?=tw=TbEytEbjX(uZrVzn4sT zI8l=gCq?JToX32unE!$^lNx8|Yi`c3$<^im6q%$f4`1glka_BsW_oS~&8PEWBgihX zFFC!F=+u1tzHdp?e#Y$nlp>cuq-qMN&nNM8a=1O@hPI7NssPf8& z#X?>FXOYmmSI8Zg^Q`81UY(%Jq3nCfv1$>Y&Q+Q2;?4ZO49Ax0d^Mx27N+mr5HIi> zoV;D2d?|i)zS?k4P;st0|Ebls(AL`~G|6@0wL<~JuJ5|d=%-0&#TWj-Q z7>iqL&2tFA36{S?0%`Gv-6xk*m=S4?>e#D?|(ZWtt+r=;NY?S zuOH=pnw!O`gT2wSJ>5;w&Q5Vbh^~&a@qQ8Q?CTM2F>$`PqrX4aEuwuw(0=iPI1rC^ zpN)w`w6E`cPj5RlvF}*4yS=BY<3fy^^~ZXRsaY^b{lZ!FsWB6Y|5`niT0r?;nz%4_Sl(As)G zf437o{tP|2Z7)5w{Z)GU`R~%k9SsxotF@*_Zi{tOj5&lMZ&IU!FQRY6&fJimn$%ij zG__95jT(L{FKc`>VrPCSJ@qSU%u460OU`FxzDYJKj~G_wi1f6ssL>^z?JJyyboQ)p z{#`ngOU`Fy{xNxZe}%IsohNvi{4uO*ru1B`c)W7mmFH8$(6+eD#|@fu=RNj=$Z(|G zc{ksDZa(!~=;y+!{Dyg_Q8nkZ3Y-rfT3olva#it78@e2dPsgvuwK5C@KmY_l00ck) z1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`; zKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck) z1V8`;KmY_l00ck)1VG?xOu(Naoxd;;SVlmXM*LIJWkstcX;vnxl(TxprIXX`^$BfB z6KQD)QmW-NwgM?lq-FgQmp{;e*Q)Qe)E5Y6NB^3U2!~7QG$}1)GG;n$lF~qk4@M|JN(&jr zmnm?$t){LQ7y&)3dL^$*4!=lqd!^iphf9;vg2d1Kl@9vYG7(am&P?PcGF-o+);Zh)Re3w~CG}Cl zIsg^bb!+3fl2TdSZX}iHpYO9RQ#o($SDs)?BPDdps& zH^)+!4QEc>$!0lU*U&IDwB%PQF2Da@<+>_eZR&71Tuzt6TED#P+xHIJzDZs;TeUe9df zaQV*f@BO<$zp?*7)2A29!;K%$%-IS1LR@?wXxf1s`MmC6upwB!d+$CCmCFtME4Vx| zR5yF~?p>$z6BqFlX^cGS9XY_gC)$$awGac(985D_s`%=l^