01-LDA e STA Bom, começamos com o básico: LDA e STA. Suponha que queiramos fazer um bloco que faz o Mario ficar grande, não importa seu estado. Para isso, precisamos do RAM address, disponível no SMWCentral. Assim, temos como a RAM que controla o powerup $19. Para fazer o Mario ficar grande, temos que ter em mente que o RAM $19 suporta esses valores: #$00-Mario pequeno #$01-Mario grande #$02-Cape Mario #$03-Fire Mario Então, começamos: LDA #$01 Simples não? Você pode fazer também LDA #xx, LDA $xx, LDA$xxxx ou LDA$xxxxxx (esses três últimos são para ram, e LDA #xx é modo decimal). Tenha em mente o fato de que você precisará de Hexadecimais, então tenha sempre aberta a calculadora do Windows no modo científico. Agora, para que o Mario fique grande, você tem que carregar o valor e botá-lo na RAM, como se você pusesse um CD no computador. Agora, para fazer o Mario ficar grande, temos o comando STA. STA pega o valor após o LDA e o transfere para o RAM. Isso quer dizer que NÃO EXISTE STA#xx ou STA#$xx. Então para o Mario ficar grande, temos: LDA #$01 STA $19 RTL Ahhh, sim. RTL. Como estou fazendo esse tutorial para criação de blocos para BTSD, uso RTL. Para o velho BT, é RTS. NUNCA SE ESQUEÇA DE TERMINAR O CÓDIGO COM RTL OU RTS OU ADEUS SUA ROM!!! RTL diz ao game que o código parou, ou seja, que o game pode continuar a executar suas ações. Então por isso RTL é importante. Como queremos um Bloco para BTSD, temos que adicionar no início do arquivo: JMP MarioBelow : JMP MarioAbove : JMP MarioSides : JMP SpriteV : JMP SpriteH : JMP MarioCape : JMP MarioFireball Explicarei a razão dos JMPs depois. Mas como são muitas labels, e queremos um bloco simples, poderemos terminar o arquivo assim: JMP Mario : JMP Mario : JMP Mario : JMP R : JMP R : JMP R : JMP R Mario: LDA #$01 STA $19 RTL ;posso OMITIR esse RTL, porque depois dessa label, a próxima SEMPRE é executada, então... R: RTL Agora, se você quer fazer o Mario ficar pequeno, voce terá que escrever LDA #$00, STA $19 e RTL? Não. Para fazer com que uma RAM tenha seu valor zero, use STZ$xx ou STZ$xxxx. NOTA: NÃO EXISTE STZ$xxxxxx! Então, nosso bloco para o Mario ficar pequeno será assim: JMP Mario : JMP Mario : JMP Mario : JMP R : JMP R : JMP R : JMP R Mario: STZ $19 RTL ;posso OMITIR esse RTL, porque depois dessa label, a próxima SEMPRE é executada, então... R: RTL Então, agora sabemos usar LDA, STA, STZ, RTS e RTL. Mas isso não é tudo. Agora temos os condicionais (branches). 02-Condicional Agora sim. Suponhamos que queremos que o Mario só fique pequeno se tiver EXATAMENTE 10 moedas. Usando somente LDA e STA não dá para fazer esse tipo de coisa. Daí, temos CMP e os Branch commands. Vamos ao assunto: CMP pega todos os valores de uma RAM e compara com o valor estabelecido. Então, temos variadas possibilidades de Branch. Agora, retornando ao problema original, escrevemos então: JMP Mario : JMP Mario : JMP Mario : JMP R : JMP R : JMP R : JMP R Mario: LDA $0DBF ;agora temos que carregar RAM, não um valor (RAM do coin counter) CMP #$0A ;compara o coin counter se o Mario tem 10 moedas BNE R ;Se o Mario não tiver 10 moedas, o código pula diretamente à label especificada, ou seja, o R (return) STZ $19 ;Como o Mario tem 10 moedas, ele fica pequeno. RTL R: RTL Então, explicando os Branches: BEQ - Só funciona se o valor comparado é IGUAL ao que está correndo na RAM. BNE - O oposto de BEQ BCC - Funciona se o valor comparado é MENOR do que está correndo na RAM. BCS - O oposto de BCC BPL - Funciona somente se o valor dentro da RAM está entre $00-$7F. Não é muito usado no nosso caso BMI - Igual ao BPL, mas funciona somente com $80-$FF. Agora, no bloco acima: se usássemos BEQ, o que o código faria? 03-Operações Suponhamos que queiramos um bloco que aumente o número de moedas do Mario. Novamente precisamos de novos opcodes. Nesse caso, temos INC e DEC. Usam a MESMA regra do STZ: Não existe INC $xxxxxx nem DEC $xxxxxx. Faça isso quando isso acontecer: LDA $xxxxxx INC (ou DEC) STA $xxxxxx Então, para fazer um bloco que adicione 1 moeda ao mario, temos: INC $0DBF RTL Simples não? Se você trocar por DEC, o que acontece? Agora, INC e DEC SÓ FUNCIONAM NO LIMITE DE BITS DE UMA RAM. Isso quer dizer que, se o valor de uma RAM for $FF, usando o INC o valor retorna a $00. DEC em $00 faz o valor ir para $FF. Mas se queremos fazer um bloco que dê 10 moedas ao Mario, escrever INC $0DBF é inútil e não funciona. Para isso existe CLC e ADC. NOTA: Quando for fazer seus blocos, NUCA SE ESQUEÇA DE BOTAR CLC ANTES E ADC. O mesmo vale para SEC e SBC, da subtração. CLC evita que o valor em ADC seja adicionado +1. O mesmo para SEC. Agora suponhamos um bloco que adicione 10 moedas ao Mario se ele não for pequeno; se ele for pequeno, o bloco RETIRA 10 moedas. Divertido efeito não? Mas vamos ao que interessa: Mario: LDA $19 BCS AddCoin ; Se o Mario for pequeno, não pula (omiti o CMP #$00; ele não é necessário) LDA $0DBF SEC SBC #10 ;é preferível usar SBC #$0A STA $0DBF ; sempre que você carrega um valor e operaciona ele, use sempre STA para botar o valor novo RTL AddCoin: LDA $0DBF CLC ADC #10 ;o mesmo com o SBC STA $0DBF RTL R: RTL Para multiplicar, use ASL. Para dividir, LSR. Mesmas regras de INC e DEC. 04-Tables Para ceros blocos, não dá para fazer apenas branches. Suponhamos que queremos um bloco que adicione moedas ao Mario dependendo de seu powerup. Ficaria cansativo, pois são QUATRO valores. Então, usamos Tables. Até agora, quando trabalhamos com blocos, usamos o registro chamado acumulador. Em tables, vamos precisar dos registros X e Y. Os comandos para eles são: LDA - LDX e LDY STA - STX e STY CMP - CPX e CPY INC - INX e INY DEC - DEX e DEY Como estamos usando ASM básico nesse tutorial, precisamos somente de LDX e LDY. Então, voltando ao nosso problema, temos: LDX $19 LDA $0DBF CLC ADC Table,x STA $0DBF RTL Mas temos que especificar os valores que serão adiconados. Como temos 4 valores em $10, temos 4 valores na Table. Tables começam com dcb e são formadas por $xx, $xx, $xx, ... Então, para o nosso código, temos: LDX $19 LDA $0DBF CLC ADC Table,x STA $0DBF RTL Table: dcb $01, $03, $05, $07 Nesse bloco, mario ganha: 1 moeda se for pequeno 3 se for grande 5 se for caped 7 se for fire Simples não? E você pode trocar LDX e ADC Table,x por LDY e ADC Table,y. Agora você também pode indexar valores. Usando o exemplo anterior, só que fazendo o Mario derrapar: LDX $19 LDA Table,x STA $86 RTL Table: dcb $40, $20, $25, $15 Bem simples, não? 05-Jumps Lembra sobre o que eu disse sobre o começo dos blocos para BTSD? Você sempre observa JMPs. Neste tópico, aprenderemos sobre os jumping opcodes. Aindo no caso do bloco que faz o Mario ficar grande, temos: LDA #$01 STA $19 RTL Podemos substituir por: LDA #$01 JSR RAM RTL RAM: STA $19 RTS Embora eu esteja trabalhando com BTSD, depois desse STA botei RTS. Por quê? Rotinas que você acessa com JSR ou JMP SEMPRE terminam em RTS (exceção são os JMPs do início dos blocos do BTSD, uma vez que são offsets), enquanto as acessadas com JSL e JML sempre com RTL. JSR também pode ser usado para pular para rotinas no DB (data bank) corrente. Mas isso não terá muita importância agora. Para pular para DBs que são importantes, usamos JSL. Por exemplo: Queremos fazer um bloco que, se o Mario for pequeno, ele é morto, mas se ele não for pequeno, perde todas as suas moedas. Como já disse, em blocos BTSD os JMPs indicam os offsets que serão usados (como os offsets que você vê no BT, mas sem os relocs, o que facilita as coisas). Então, voltando ao código, temos: (Quero que esse bloco funcione somente se o Mario o tocar por baixo e por cima) JMP Check : JMP Check : JMP Return : JMP Return : JMP Return : JMP Return : JMP Return Check: LDA $19 BNE EraseCoins JSL $00F606 ;Tem SEMPRE que ser JSL $yyxxxx (long addressing); esse valor eu achei no ROM Map; mata o Mario RTL EraseCoins: STZ $0DBF RTL Return: RTL JMP funciona como JSR, mas IGNORA o que foi escrito depois. A mesma coisa entre JML e JSL. Exemplo (só valores inúteis): JSR Rand_V (ou JSL) ;code JMP Rand_V (ou JML) ;code Comentários, Ponto-e-vírgula e Dois-pontos em ASM Não há muito o que dizer. Nem precisa seguir. Você normalmente vê um código em opcodes alinhados linha a linha, mas você pode fazer isso também: LDA #$xx : STA $yy : RTL Mas isso é uma perda de tempo e não é aceito no SMWCentral. Então use dois-pontos somente nas offsets determinadas pelos JMPs em blocos BTSD. Você pode comentar seu código. Depois de um opcode que você queira comentar, adicione ";", "\", "|" ou "/." Eles são ignorados na leitura do código. Mas antes dos sinais "\|/" sempre bote ";". Evita erros. Botar ; ANTES de um opcode o ignora, então cuidado. Exemplo de comentários: LDA $0DBF ;RAM do contador de moedas CMP #$15 ;se Mario não tiver MAIS de 15 moedas BCS NoHurt ;ele se fere JSL $00F5B7 ;Subrotina de ferimento LDA #$04 ;\Som do Mario encolhendo STA $1DF9 ;/ RTL ;fim do código NoHurt: RTL Definições Em alguns blocos do SMWCentral, há lugares onde se tem um valor que você pode mudar, e em sprites também. basta dar uma olhada nos custom bosses do Maxx e do Iceguy. São valores definidos, e num bloco, você pode colocá-los SEMPRE depois dos JMPs. Exemplo. Um bloco que faça um determinado som. Se você quer que outros editem o som sem se perderem, você pode colocar dessa maneira: !SOUND = $04 !RAM = $1DF9 LDA #!SOUND STA !RAM RTL 06-Introdução ao binário Além de decimais e hexadecimais, o ASM suporta binários. Binários são números compostos apenas por 0 e 1, em casas com potência de 2. O máximo que o ASM comporta de valor é 1111 1111 ($FF). Lembra do comando LDA? Podemos ter também LDA #%01000100, por exemplo, que é o mesmo que LDA #$44. Basicamente, temos: #%00000001 = #$01 #%00000010 = #$02 #%00000100 = #$04 #%00001000 = #$08 #%00010000 = #$10 (#16) #%00100000 = #$20 (#32) #%01000000 = #$40 (#64) #%10000000 = #$80 (#0128) #%11111111 = #$FF (#0255) (valor máximo) Então, como trabalhamos com o binário? Simples. Suponhamos que tenha um bloco que teleporta se você apertar o botão para cima: LDA $15 CMP #$08 BNE R LDA #$06 STA $71 STZ $89 STZ $88 R: RTL O problema desse código é que ele roda SOMENTE se você apertar o botão para cima. Suponhamos que eu apertei um outro botão: Só com o botão para cima: 00001000 - #$08 Com outro botão genérico: 00001100 - #$0C! Então temos a opção de usar AND. AND IGNORA outros bits, então, para esse bloco, temos: LDA $15 AND #$08 BEQ NoUp LDA #$06 STA $71 STZ $89 STZ $88 RTL NoUp: RTL Simples não? AND também operaciona: LDA #%0101 AND #%0011 RE.: #$0001 AND só converte um bit a 1 nessa operação se o bit em LDA e em AND for 1. Lógica do E: Só é verdade se os DOIS forem verdade. Ex.: LDA $13 AND #$07 BNE Skip INC $19 Skip: RTL Esse código faz com que a cada frame de animação, o powerup do Mario sobe, até parar no frame 8 (#$07+1). Só vou cobrir essa parte do AND. ORA e EOR são quase inúteis num bloco, mas vou ensiná-los para nosso uso. ORA também faz operações: LDA #%0101 ORA #%0011 RE.: #%0111 ORA segue a lógica do "OU": Só e falso se todos forem falsos. ORA também checa se mais de uma RAM address é zero. Exemplo: Um bloco que zera o contador de moedas do Mario se ele tiver na opção OFF (bloco ON/OFF), em cima do Yoshi e dentro da água: LDA $14AF ORA $187A ORA $75 BEQ Return STZ $0DBF RTL Return: RTL Esse código zera o contador de moedas se os bits das RAM com LDA e ORA NÃO sejam zero. Se todos forem zero, ele simplesmente retorna. EOR não faz muita coisa em blocos, só inverte bits. Operação com EOR: LDA #% 0101 EOR #% 0011 RE.: #% 0110 Simples. Se os dois bits forem IGUAIS, EOR os torna zero. EOR é usado em sprites, quando você quer flipá-los se eles tocam uma parede. TRB e TSB Simples: esses dois opcodes servem para tornar qualquer bit de uma RAM 0 (TRB) ou 1 (TSB). Suponhamos que queremos um bloco que faça o Mario pular se um fireball bter nele. Aqui estamos: JMP Return : JMP Return : JMP Return : JMP Return : JMP Return : JMP Return : JMP Fiery Fiery: LDA #$80 ;Bit dos botões B e A, que fazem o Mario pular TSB $15 ;Ativar bit na RAM RTL Return: RTL P=nvmxdizc Não há muito o que dizer sobre essa flag, o processador de SNES, mas há coisas muito úteis. Antes de mais nada, explicando as letras: n - Negative Flag - será 1 se houver valores entre $80 e $FF, senão será 0. v - Overflow Flag - subtrações de Hexadecimais $80-$FF que deem valores $00-$7F o ativam. Usado em branches (BVC e BVS) m - Muda o Acumulador de 8-bit para 16-bit e vice-versa (1 é 8-bit, então em nosssos códigos atuais, m=1) x - Muda o Index de 8-bit para 16-bit d - MOdo decimal. Ativado com SED e desativado com CLD. Usando SED, o código operará somente em decimal. i - Modo Interrupção. Ative com SEI e desative com CLI. Até existe um opcode de retorno, RTI!!!! Mas não use esse bit. z - Zero flag. Ativado se o valor do Acumulador é zero. c - Carry Flag. O nono bit de um valor ASM. NOrmalmente, se ativa um desses pedaços do processador com dois comandos: REP e SEP. Exemplo: Os Shop Blocks para 6-digit coin counter patch. Usando só CLCs e ADCs não daria para tirar, por exemplo, 256 moedas ($0100), porque o acumulador é 8-bit, então só suportaria valores de $00 a $FF. Então, é ativado o modo 16-bit. Como é o bit #$20 do Processador, e 16-bit o desativa, é colocado antes da subtração das moedas: REP #$20 ;Còdigo SEP #$20 Mas só isso nos será útil agora: o 16-bit. DICAS 1) Adicionando coins: LDA $0DBF CLC ADC #$xx STA $0DBF RTS ; Não use esse ou terá glitches quando o counter passar de 100! Use esse abaixo: LDA #$xx STA $13CC RTS 2) Machine cycles: Evite usar long addressing quando o bank da RAM for 7E, por que assim, além de acelerar o bloco, ainda torna possível o uso de STZ, que optimiza ainda mais o bloco. LDA #$00 STA $7E0019 ; 7 ciclos RTS ;Tirando Long addressing LDA #$00 STA $19 ; 5 ciclos RTS ; Usando STZ STZ $19 ; 3 ciclos RTS 3) Blocos que você ativa por baixo, mas só quer que eles ativem uma só vez: Um bloco Mario-Luigi, pra funcionar direito, depois do código normal, tem que adicionar: LDA #$20 STA $7D RTS senão, quando você encosta no bloco, vai ficar trocando várias vezes entre o Mario e o Luigi sem permitir seus movimentos! 4) Não usar os seguinte opcodes: BRK - Significa "Break System" COP - Quebra a ROM SEI, CLI e RTI - Interrupt Flag WAI - Espera por interrupção XCE - Ativa modo emulador STP - Congela o jogo NOP - Não operaciona. Só usado em patches, para tirar certas rotinas do jogo, como "Disable Score". Stack Como fazer pra recuperar um valor depois de usá-lo? É aí que entra o Stack. Stack é como uma estante de bytes, onde você adciona ou tira "pacotes" de bits. Para isso, usamos para o acumulador e os registros X e Y: Para colocar um valor no Stack: PHA, PHX, PHY Para tirar um valor do Stack: PLA, PLX e PLY Agora, qual é a função do Stack? Preservação de valores: LDA #$44 ; Stack = xx xx xx xx xx ... PHA ;\ Stack = xx xx xx ... xx $44 ;código ;/ PLA ;Stack = xx xx xx xx xx ... Há outros opcodes Stack. Vamos nos focar Em três opcodes Stack para sprites: PHB - Pega um 8-bit data bank e o coloca no Stack. Versão contrária: PLB PHK - Pega o bank 8-bit corrente e o coloca no Stack. Versão contrária: Nenhuma (use PLB) Use esses três na criação de sprites, após o dcb "MAIN": dcb "MAIN" PHB PHK PLB JSR SPRITE_ROUTINE PLB RTL Outros opcodes stack: PHP - Pega o status do P=nvmxdizc e o coloca no Stack. Versão contrária: PLP PER - Pega o valor 16-bit de um label. Use como PER Label PHD - Pega o valor 16-bit da página direta de registro e o coloca no Stack. Versão contrária: PLD. Há mais opcodes, mas são quase inutilizados em blocos. BIT Test Vamos a um dos comandos mais complicados: BIT. O que ele faz? Mexe com o P=nvmxdizc. Mas como? Vamos supor que o valor do acumulador na RAM $0DBF seja $40 (em Hex). Agora, fazemos: BIT $0DBF Vamos ao que interessa nessa tabela: P=nvmxdizc N V M X D I BIT test Z C 80 40 20 10 08 04 02 01 Então, se o Valor em $0DBF é $40, e fazemos BIT $0DBF, estamos ativando a bit V do processador. Ou seja: Overflow Flag. Por exemplo: Um bloco que só deixa o Mario passar se ele tiver mais do que 64 moedas. Ele é assim: LDA $0DBF CMP #$40 BCS Return LDY #$01 LDA #$30 STA $1693 RTL Return: RTL Usando BIT: BIT $0DBF (se A>=$40, V=set) BVS Return (se V=set, branch) LDY #$01 LDA #$30 STA $1693 RTL Return: RTL XBA XBA simplesmente FLIPA o valor da RAM. Exemplo: LDA $0080 XBA RAM=$8000 Taí. mais dois opcodes pro nosso conhecimento ASM... Transferência de Registros Usamos os comandos de transferência para colocar valores de um registro em outro. Pricipais opcodes: TAX Copia o Acumulador em X TAY Copia o Acumulador em Y TYX Copia Y em X TXY Copia X em Y TXS Copia X no Stack. Como se fosse um PHX TXA Copia X no acumulador TYA Copia Y no Acumulador TSX Copia o Stack em X. Diferente de PLX TCS Copia o Acumulador no Stack. Como um PHA TSC Copia o Stack no Acumulador. Diferente de PLA TCD Copia o Acumulador no registro de página indireta 16-bit TDC Inverso de TCD Exemplo: LDA #$40 ; A=$40 e X=$F0 TAX ; A=$40 e X=$40 Usados muito em sprites, principalmente em dcb "INIT". SUB_HORZ_POS se encontra no registro y, mas não daria para fazer STY $157C,x. Então, logo após JSR SUB_HORZ_POS, coloca-se TYA STA $157C,x. LDA ($xx) Essa é complicada. Vamos ter que exemplificar: Usemos LDA ($18). Vamos supor que: $18=$80 $19=$02 LDA ($18) faz o seguinte: LDA [$18+$19] ou seja, sendo a soma dos valors RAM igual a $82, temos: LDA ($18) = LDA $82! É pouco prático, mas eficaz quando se trabalha com RAMs múltiplas...