Koodia on tähän mennessä kertynyt vähän päälle 600 riviä. Se on aika tiivistä. Vähän ilmaa on jänyt kohtiin, joita ei ole vielä tehty, mutta on otettu silti huomioon.
Nykytilanne lyhyesti:
Vain kaksi romia toimii tiedetysti: fighter f8000 ja zelda title screen simulator. Jälkimmäinen toimii täysin spritejä lukuunottamatta ja ensimmäisestä toimii valikko ja pelin osalta tausta.Mikään ei toimi vielä pelattavasti. Fps on noin yksi, useinmiten alle sen. Nopeammilla koneilla on potentiaalia yksinkertaisempien pelien osalta jopa 60fpsään, mutta tavoitefps on noin kymmenen.
Pääoptimointikeino tulee olemaan HLE eli high level emulation. Se meinaa tiettyjen komentosarjojen korvaamista suoraan niiden toteuttamisella komennontarkan emuloinnin sijaan. Project64 on tunnettu hle:n hyödyntäjä. Se sai muistaakseni nelinkertaistettua suorituskykynsä sillä.
Tällä hetkellä noin kaksi kolmasosaa virallisista komennoista on lisätty. Värit toimivat. Taustan piirto toimii. Spritet eivät toimi. Ohjaimen luku antaa randomin nappiyhdistelmän. Yhtäkään mapperia ei ole lisätty. Ne fallbackaavat NOMAPPERiin, mutta niiden ohjelmointi on otettu huomioon.
Emulaattorin rakenne
Käynnistys.
-Emulaattori analysoi romin, merkitsee mapperin ja muut tiedot ylös, lataa ohjelman omaan muistipalaansa ja grafiikkamuistin toiseen.
-Muistia alustetaan romille, ramille, vromille ja vramille. spriterami puuttuu tällä hetkellä, kuten kaikki muukin niihin liittyvä.
-Alustetaan kaikki prosessorin ja näytönohjaimen tarvitsemat globaalit.
-Ladataan paletti ja valmistellaan maskit optimoitua renderöintitapaa varten, sekä varataan kuva ruudulle piirtoa varten.
Emulointi.
Prosessorin emulointi tapahtuu tapahtuu isolla selectillä komento kerrallaan. Loopin alussa luetaan komento ja select valitsee sen komennon koodin.
Koodin kuuluu lisätä program counteria komennon viemä määrä tavuja ja syklilaskuria myös oikea määrä. Ne ilmenevät 6502-prosessorin dokumentaatioista. Olen käyttänyt tätä: http://www.obelisk.demon.co.uk/6502/reference.html
esimerkki:
Code: Select all
Case 169'A9 LDA Immediate
A=readmem(pc+1)
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+2
cycle+2
Yleensä kirjoitan myös heksan ja komennon tiedot kommenttiin.
Jos komentoa ei ole lisätty, niin emulaattori printtaa komennon punaisena sekä desimaalina että heksana.
Code: Select all
Default'jos komentoa ei löytynyt
cycle+1
pc+1
Color 255,0,0
Print op+" "+Right(Hex(op),2)
'Wait 2000
Prosessorin emuloinnin jälkeen on tällainen pätkä:
Code: Select all
'Print op+" "+Right(Hex(op),2)
Color 255,255,255
ppu()
'printstatus()
'If N>1 Or N<0 Then Wait 5000
'WaitKey
'If KeyDown(cbkeyp) Then printstatus()
'If Not KeyDown(cbkeyspace) Then WaitKey' Else Wait 1'50
//vblankin generointi
If cycle<4 Or cycle>(cycle2+1789773/60) Or KeyDown(cbkeyv) Then cycle2=cycle:ppu_vblank=1:Print "vblank"':Wait 2000
Vblankin generointi tarkoittaa sitä että kuva piirretään joka framen lopuksi tai poikkeuksellisesti silloin kun painetaan v-kirjainta. Tuosta voisi ehkä poistaa cycle2-jutun, se on siinä vain sen vuoksi että television päivitystaajuus ei mene tasan prosessorin taajuuden kanssa.
ppu() piirtää ruudun jos vblank on tullut. Sitä kutsutaan joka kierroksella sen vuoksi, että jotkut nes-ohjelmat muokkaavat videomuistia piirtämisenkin aikana tiettyjen tulosten saamiseksi. optimoitu rendermetodi ei toistaiseksi tue mitään niistä eikä sillä ole mahdollisuuksiakaan toteuttaa niistä kaikkia, mutta rendermethod0lla on mahdollisuus tukea niitä. Nyt kuitenkin koko ruutu piirretään vain vblankin yhteydessä, joten se on siellä melkein turhaan.
Muistin luku:
Nesissä on enemmän muistiosotteita, kuin muistia. Lisäksi muistialueet voivat osoittaa hyvin erilaisiin juttuihin ja monet muistipaikat osoittavatkin samaan. Osa muistipaikoista ei edes ole varsinaista muistia, vaan toimittavat jotain muuta tehtävää, kuten äänen tai näytön ohjaus tai ohjaimen tilan luku. Siksi muistin lukuun tarvitsee oman väliohjaimen. Sen tehtävää hoitaa readmem(). Siellä tapahtuu myös mapperikohtainen kikkailu, mistä lisää myöhemmin.
Jos muistialuetta ei ole niin palautuu väylään viimeksi työnnetty arvo eli muistipaikan ylempi tavu.
funktio on jaettu iffeillä selkeästi muistialueisiin. siellä on rekisterien käsittely paikallaan, vaikka niitä ei olekaan erityisemmin lisätty. Mappereillekin on paikka valmisteltu.
Muistin kirjoitus:
Rakenne on sama kuin muistin luvullakin. Muistin kirjoituksessa mappereilla on kuitenkin suurempi osa. Niiden kontrollointi nimittäin tapahtuu kirjoittamalla romin osotteisiin. Niitä ei ole vielä koodattu, mutta paikka on valmiina.
ppu() eli picture processing unit eli näytönohjain:
ppu:ssa on koodattuna kaksi ruudunpiirtotapaa. bitti kerrallaan toimiva, joka mahdollistaa monimutkaisten efektien toimimisen myöhemmin. Se tosin on niin hidas ettei sitä kannata vakavissaan käyttää
Toinen renderöintitapa toimii taas tile kerrallaan ja melko erikoisesti. Ojelman käynnistyessä koko videomuisti esilasketaan neljäksi maskikuvaksi jokaista paletin väriä kohden. sitten piirtäessä tehdään neljä palettikuvaa, joihin on piirretty palettia vastaavat värit kullekkin palettialueelle. Sitten esilasketuilla maskeille rajataan näistä näkyvät alueet ja piirretään päälekäin lopulliseen nkyvään kuvaan. Tämä tekniikka osoittautui kyllin nopeaksi reaaliaikaiseen kuvan näyttämiseen nesin virallisilla säännöillä. Epäviralliset saavat tyytyä hitaampaan.Kenties silläkin voisi yrittää jotain hiukan vastaavaa. en kyllä tiedä että kannattaako tämä enää yksirivisillä parin pikselin kuvilla.
Nesin toiminta tarkemmin:
Prosessori
Nes käyttää muokattua versiota 6502-prosessorista. Suurin ero on, ettei se käytä 10-järjestelmää, vaikka D-flagi olisikin päällä. Muuten toiminta taitaa täsmätä. Kyse on kuuluisasta ja hyvin suositusta 8-bittisestä prosesorista.
Prosessorissa on kolme rekisteriä A,X ja Y. A on yleisrekisteri ja X:ll ja Y:llä on hiukan erikoistehtäviä ja ne ovat vähemmän yleisiä. Prosessori käyttää näitä tavallaan sisäisinä muuttujina. Niihin yleensä ladataan dataa muistista prosessointia varten ja tuupataan sitten tulokset takas jos on tarvis.
Näiden lisäksi on tilaflageja, jotka vaikuttavat joidenkin komentojen toimintaan. Ne yleensä ilmoittavat jotain laskutoimitusten tulosten laadusta ja mahdollistavat ohjelman kulun ohjaamisen niiden mukaan ja esimerkiksi useampitavuisten laskujen suorittamisen. Flageja ovat C, Z, I, D, B, V, N ja niiden komentokohtainen toiminta selviää referenssistä.
Prosessorissa on muistaakseni vähän yli 80 komentoa virallisesti, sekä muutamia epävirallisia komentoja, jotka ovat syntyneet vahingossa suunnittelematta. Tämä meinaa sitä että noin puolet komennoista on käyttökelvottomia. Ne ovat siis vapaita HLE-käyttöön.
Epävirallisista komennoista: http://wiki.nesdev.com/w/index.php/Prog ... al_opcodes
ja tietysti viralliset myös: http://www.obelisk.demon.co.uk/6502/reference.html
Osoitusmoodit adressing modes
Monesta komennosta on useita versioita, jotka osoittavat muistiin eri tavoin.
Empä jaksanutkaan kirjoittaa: tässä ovat. koodista voi katsoa myös mallia.http://www.obelisk.demon.co.uk/6502/addressing.html
Zero page on ainakin hyvä tietää. Se tarkoittaa ensimmäistä 256 tavua muistissa. Siitä on hyötyä nopeammassa muistin käsittelyssä.
Muisti
Nesin muistin tarkempi jakautuminen näkyy wikipediasta: http://en.wikibooks.org/wiki/NES_Progra ... memory_map
Päähuomiot ovat Zero pagessa, stackissa eli pinossa, rekisterialueissa, muistialueiden peilauksessa ja romissa. lisäksi ON mapperista riippuvia erikoisalueita.
Pino sijaitsee ihan normaalisti muistissa, joten sitä voi erikoistarkoituksiin muokata muutenkin kuin pinokomennoilla.
Ramia ei ole ruhtinaallisesti, joten käyttämätöntä muistia jää paljon. Tämä alue on "korjattu" osoittamalla zero pageen, stackiin ja ramiin aina vain uudestaan. kaikkiaan neljästi.
Näiden jälkeen tulee rekistereitä ja niiden peilit ja lisää reksitereitä.
Sitten tulee mapperispesifiä tavaraa ja itse ohjelmamuisti. Se on jostain syystä kahdessa palassa. Varmaan sen takia että toista voi sulavasti vaihdella mapperilla kun toista suoritetaan. Näin mahdollistuu suurienkin ohjelmien suoritus vähäisellä osoitettavalla muistilla.
Aivan viimeisillä muistipaikoilla opn pari erikoisosoitetta. Ne ovat ohjelmaromissa. Tämän vuoksi yksimuistipankkinen ohjelma ladataan aina jäljempään pankkiin jos oikein muistan.
Ne ovat muistiosoitteita eri tapauksien käsittelyyn. Yksi niistä kertoo, mistä ohjelman suoritus pitää aloittaa.
Rekisterit
Nesillä on kaksi settiä rekistereitä muistialueilla $2000–$2007 ja $4000–$401F
niiden tarkoitukset selviävät pääosin täältä: http://en.wikibooks.org/wiki/NES_Progra ... .2FO_ports
Rekistereitä käytetään kirjoittamalla niihin tai lukemalla niistä. Niiden käytöstä ja koodauksesta löytyy jo hiukan esimerkkiä emulaattorista. PPU:n kirjoituksesta on nimittäin osa toteutettu ja ohjaimen luku palauttaa randomin näppäinasetelman. Ohjaimen luvussa jokainen bitti tarkoittaa yhtä nappia, joten randomitavu tarkoiottaa randomia näppäinyhdistelmää.
PPU
PPUhun pääsee käsiksi kirjoittelemalla ja lukemalla tiettyjä rekistereitä. Sen muisti on erillinen prosessorin muistista. Nesissä on kahdeksan neljän värin palettia, jotka valitaan 6-bittisestä pääpaletista. Puolet niistä on taustalle ja puolet spriteille. Kaikki grafiikka koostuu 8x8 tileistä, jotka tulevat romin mukana. Taustalle voi asettaa paletin 16x16 alueita kohden. Tilejä on kaksi 256 palikan pankkia käytettävissä. Toinen niistä on taustalle ja toinen spritelle. niiden paikkaa voi vaihdella keskenään ja mapperi voi vaihtaa niitä myös. Nesin käytössä on mapperista riippuen 2 tai 4 näytöllistä muistia taustan esittämiseen. tämä mahdollistaa skrollauksen.
MAPPERIT
Mapperit ovat nes-kaseteissa olevaa omaa rautaa, joka mahdollistaa enemmän ohjelma- ja grafiikkamuistin käytön, kuin osoitettavaa muistia olisi, sekä kaikenlaisia erityiskikkoja kuten jaetun ruudun ja muuta erikoista. Niitä ohjataan kirjoittamalla ohjelmaromiin. Mappereita on satoja, mutta muutamalla yleisimmällä pääsee jo pitkälle. Niiden päätehtävä on muistialueiden osoituksen muuttelu, mutta ne pystyvät kontrolloimaan myös ruudun jakamista(=mm. alapalkki) triggeröimällä keskeytyksen ajastetusti tai jopa laajentamaan näytönohjaimen kykyjä.
HLE
Hle on tarkoitus toteuttaa esikäsittelemällä koodi ja korvata hidastavat kooinpätkät omilla komennoilla. Siihen käytetään prosessorin käyttökelvottomia komentoja, jotka ovat vapaita. Tämä mahdollistaa reaaliaikasen nes-emulaattorin coolbasicilla.
Ensimmäinen ja helpoin optimoitava on zeldatitlescreenissä olevan idleloopin korvaaminen suoraan vblankiin hyppäämiseen. Toinen on taas vblankin odotus. sitten voi varmaan kirjoittaa jonkun analysointipätkän, joka selvittää että mihin koodinptkiin kuluu eniten aikaa ja niitä korvaamalla saa sitten enemmän nopeutta.
Äänet
Nes käyttää viittä äänikanavaa. Kahta kanttiaaltoa, yhtä kolmio-aaltoa, noisea ja sampleja soittavaa kanavaa.
Kantti-aalloista voi vaihtaa taajuutta, voimakkuutta ja kantin pituuksien suhdetta. Kolmio-aallossa voi muuttaa taajuutta. Noisessa voi muuttaa päivitysnopeutta(=korkeus) ja toistoa(=metallinen ääni). Samplet ovat yksibittisiä. Niitä käytetään enimmikseen rummuissa, mutta joissan tapauksissa jopa puheessa.
Kaikki nesin äänet on tehty näitä yhdistelemällä. Yhdeltä kanavalta voi kuulua vain yksi ääni kerralla. Eri kuuloisia ääniä saa aikaan säätelemällä taitavasti kanavien eri parametreja ja kenties yhdistelemällä. Myös tremolo on yleinen keino saada enempi irti nesin äänistä. Se tarkoittaa sointujen muodostamista toistamalla sävelet nopeasti peräkkäin samalla kanavalla.
Romit
Tämä emulaattori lukee romeja ines-muodossa. Se ei välitä päätteestä, kunhan sisältö on oikein.
Formaatin dokumentaatiota löytyy täältä: http://wiki.nesdev.com/w/index.php/INES http://nesdev.parodius.com/neshdr20.txt
Heksadesimaaleista
Kaikki nes-dokumentaatio käyttää heksadesimaaleja, koska ne ovat luonnollisia bittiympäristössä. Ne menevät tasan tavujen ja bittien kanssa ja niistä näkee suoraan yksittäisten bittien asennon vain katsomalla.
Heksadesimaalijärjestelmässä on kuusitoista merkkiä kymmenen sijaan. yksi heksanumero vastaa neljää bittiä. merkit ovat 0-9 ja näiden päälle vielä A-F. Laskusäännöt ja logiikka on täysin sama kuin kymmenjärjestelmässäkin. Kymmenjärjestelmässä tulee uusi numero lukuun mukaan aina kun tullaan uuteen kymmenpotenssiin. Heksa, eli 16-järjestelmässä sama ilmiö tuleekin kuudentoista potensseilla koska numeroita on 16. Vertaa: 9->10, F->10 ja 99->100, FF->100. F opn kymmenjärjestelmässä 15, joten 10 on 16 kun muutetaan heksajärjestelmästä kymmenjärjestelmään. FF on taas 255 (16^2-1) ja 100 on sitten 256. Merkkien lisääntyessä niiden painoarvo aina 16-kertaistuu. Heksa-arvo 543 muutetaan kymmenjärjestelmään näin: 5*16^2+4*16+3. Tästä tulee 1347. AB opn taas 10*16+11=171. Sen laskikin nopeasti päässä
Coolbasicissa ja windowsin laskimessa on kätevät muuntotoiminnot heksoille. Winxp:ssä muuntotoiminto löytyy kun laskimen valikosta valitseee funktiolaskin ja uudemmista taitaa löytyä ohjelmointilaskimena. Coolbasicissa on Hex()-funktio, jolla saa luvusta nopeasti heksan. Toiseen suuntaan toimiva funktio löytyy varmaan CBrepositorystä.
Heksat on nesin yhteydessä tapana merkitä edessä olevalla dollarimerkillä ettei niitä sekoita kun niissä ei ole aina kirjaimia mukana.
Coolbasic ei ymmärrä heksoja oikein vaikka se onkin ymmärtävinään. Osan se ymmärtää oikein ja osaa ei. Siksi olen käyttänyt koodissa kymmenjärjestelmää.
Todo
-Loput komennot
-Spritet
-Ohjaus
-HLE
-Mappereita
-Loogisten bittioperaatioiden vaihtaminen inlinestä esilaskettuihin. [TEHTY]
Pätsien postaaminen
Jotta ei tulisi sekaannuksia, niin minusta olisi parasta että uudet koodinpätkät lähetetään pelkilteen ja aihealuetain. Uudet komennot erikseen, mapperikoodinpätkät paikan mukaan ja rekisteritoiminnot paikan mukaan. Ei ole pakko, mutta se helpottaa koodien yhdistämistä jos useampi tekee jotain yhtäaikaa. Mukaan myös koko koodi ja kasvava versionumero ja se minkä pohjalta on tehnyt että on helppo jatkaa ja varmistaa että kaikki lisäykset säilyy.
Rev.1
Code: Select all
Jouduin poistamaan kun meni maksimimerkkimäärä yli
Hosuin tuon äskösen kanssa. Sillä ei enää toiminut zelda title screen. Samalla esimerkki muokkauksien lähetyksestä.
Rev.2
Code: Select all
SCREEN 600,300
'avaa rom
file$="zeldatitlescreen\zelda.nes"
'file$="random\city.nes"
'file$="fighter_f8000\fighter_f8000.nes"
'file$="soda cans\sprite.nes"
'file$=CommandLine()
//headerin käsittely
f=OpenToRead(file$)
size=FileSize(file$)
Global rombanks,vrombanks,mapper
headercheck=(ReadInt(f)=441664846)
rombanks=ReadByte(f)
vrombanks=ReadByte(f)
t=ReadByte(f)
mapper=t Shr 4 + ReadByte(f) Shr 4
mirroring=t Mod 16
'infoa
Print "headerin tarkistus: "+headercheck
Print "rombanks: "+rombanks
Print "vrombanks: "+vrombanks
Print "mapper: "+mapper
Print "mirroring: "+mirroring
Print "tiedostokoko: "+size+"(muistia "+(rombanks*16384+vrombanks*8192)+")tavua"
//luetaan muistipalikoihin
SeekFile f,16
Global rom,vrom,ram,ppuram
rom=MakeMEMBlock(rombanks*16384+1)
vrom=MakeMEMBlock(vrombanks*8192*2*2)
For i=0 To 16384*rombanks
PokeByte rom, i, ReadByte(f)
Next i
For i=0 To 2*8192*vrombanks-1
PokeByte vrom, i, ReadByte(f)
Next i
ram=MakeMEMBlock(2048)
ppuram=MakeMEMBlock(16380)
//flagit ja rekisterit
Global C,Z,I,D,B,V,N'flageja asmidokumentissa n on s statusrekisteristä
Global A,X,Y'rekisterit
Global S'stack
Global ppu_vblank,ppuaddr_addr,ppuaddr_addr0,ppuaddr_addr1,ppuaddr_turn
Global cycle,PC
//piirto
Global rendermethod
rendermethod=1
Dim renderlayers(7)
Dim maskedtiles(3)
Global scr
scr=MakeImage(256,240)
Dim palette(64)
palette(0)=8158332:palette(1)=252:palette(2)=$188:palette(3)=4466876
palette(4)=9699460:palette(5)=9699460:palette(6)=11014144:palette(7)=8918016
palette(8)=5255168:palette(9)=30720:palette(10)=26624:palette(11)=22528
palette(12)=16472:palette(13)=0:palette(14)=0:palette(15)=0
palette(16)=12369084:palette(17)=30968:palette(18)=22776:palette(19)=6833404
palette(20)=14155980:palette(21)=14942296:palette(22)=16267264:palette(23)=14965776
palette(24)=11303936:palette(25)=47104:palette(26)=43008:palette(27)=43076
palette(28)=34952:palette(29)=0:palette(30)=0:palette(31)=0
palette(32)=16316664:palette(33)=3980540:palette(34)=6850812:palette(35)=9992440
palette(36)=16283896:palette(37)=16275608:palette(38)=16283736:palette(39)=16556100
palette(40)=16300032:palette(41)=12122136:palette(42)=5822548:palette(43)=5830808
palette(44)=59608:palette(45)=7895160:palette(46)=000000:palette(47)=000000
palette(48)=16579836:palette(49)=10806524:palette(50)=12105976:palette(51)=14203128
palette(52)=16300280:palette(53)=16295104:palette(54)=15782064:palette(55)=16572584
palette(56)=16308344:palette(57)=14219384:palette(58)=12122296:palette(59)=$12122328
palette(60)=64764:palette(61)=16308472:palette(62)=$000000:palette(63)=$000000
For i=0 To 3 'rendermethod 1 juttuja
renderlayers(i)=MakeImage(256,240)
MaskImage renderlayers(i),cbmagenta
Next i
For i=4 To 7
renderlayers(i)=MakeImage(256,240)
MaskImage renderlayers(i),cbblack
Next i
'prerendataan tilet
For i=0 To 3
maskedtiles(i)=MakeImage(512*8*vrombanks,8)
drawtoimage maskedtiles(i)
For j=1 To 512*vrombanks
For y=0 To 7
byte1=PeekByte(vrom,j*16-1+y)
byte2=PeekByte(vrom,j*16+7+y)
For x=0 To 7
If Not (((byte1 Shr (7-x)) Mod 2))+((((byte2 Shr (7-x)) Mod 2))Shl 1)=i Then PutPixel j*8+x,y,255 Shl 16 +255
'PutPixel j*8+x,y,((byte1 Shl (25+x)) Shr 31 + (byte2 Shl (25+x)) Shr 30)*64
Next x
Next y
'If (j Mod 4) then Line j+i,0,j+i+8,8
Next j
DrawToScreen
Next i
Global aika
//emulaattori
pc=readmem(65533) Shl 8 + readmem(65532)'reset-osoitin, eli aloituskohta osoitteesta $FFFE
Print "alkuosoitin: "+pc
running=1
While running
op=readmem(pc)
Select op
Case 9'09 ORA
par=readmem(pc+1)
A=((A Shr 7) Or (par Shr 7)) Shl 7 +((A Shl 25 Shr 31) Or (par Shl 25 Shr 31)) Shl 6 +((A Shl 26 Shr 31) Or (par Shl 26 Shr 31)) Shl 5+((A Shl 27 Shr 31) Or (par Shl 27 Shr 31)) Shl 4+((A Shl 28 Shr 31) Or (par Shl 28 Shr 31)) Shl 3+((A Shl 29 Shr 31) Or (par Shl 29 Shr 31)) Shl 2+((A Shl 30 Shr 31) Or (par Shl 30 Shr 31)) Shl 1+((A Shl 31 Shr 31) Or (par Shl 31 Shr 31))
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7
pc+2
cycle+2
Case 10'0A
C=A Shr 7
A=(A Shl 1) Mod 256
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7
pc+1
cycle+2
Case 16'10 BPL Relative
If N=0 Then
param=readmem(pc+1)
oldpc=pc
pc=pc+1+(param Mod 128)-((param>127)*127)
cycle=cycle+3+(RoundDown((oldpc-32768)/16384.0)<>RoundDown((pc-32768)/16384.0))
Else
pc+2
cycle+2
EndIf
Case 24'18 CLC
C=0
pc+1
cycle+2
Case 32'JSR
writemem(256+S,(PC+2) Shr 8)
S=(256+S+1) Mod 256
writemem(256+S,(PC+2) Mod 256)
S=(256+S+1) Mod 256
pc=readmem(pc+2) Shl 8 + readmem(pc+1)
cycle+6
'pc+3
Color cbmagenta
Case 41'29 AND Immediate
par=readmem(pc+1)
A=((A Shr 7) And (par Shr 7)) Shl 7 +((A Shl 25 Shr 31) And (par Shl 25 Shr 31)) Shl 6 +((A Shl 26 Shr 31) And (par Shl 26 Shr 31)) Shl 5+((A Shl 27 Shr 31) And (par Shl 27 Shr 31)) Shl 4+((A Shl 28 Shr 31) And (par Shl 28 Shr 31)) Shl 3+((A Shl 29 Shr 31) And (par Shl 29 Shr 31)) Shl 2+((A Shl 30 Shr 31) And (par Shl 30 Shr 31)) Shl 1+((A Shl 31 Shr 31) And (par Shl 31 Shr 31))
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+2
cycle+2
Case 42'2A ROL
T=A Shr 7
A=A Shl 1 - T Shr 8 +C
C=T
pc+1
cycle+2
Case 44'2C BIT
par=readmem(pc+1)
T=((A Shr 7) And (par Shr 7)) Shl 7 +((A Shl 25 Shr 31) And (par Shl 25 Shr 31)) Shl 6 +((A Shl 26 Shr 31) And (par Shl 26 Shr 31)) Shl 5+((A Shl 27 Shr 31) And (par Shl 27 Shr 31)) Shl 4+((A Shl 28 Shr 31) And (par Shl 28 Shr 31)) Shl 3+((A Shl 29 Shr 31) And (par Shl 29 Shr 31)) Shl 2+((A Shl 30 Shr 31) And (par Shl 30 Shr 31)) Shl 1+((A Shl 31 Shr 31) And (par Shl 31 Shr 31))
If T=0 Then Z=1 Else Z=0
N=par Shr 7
V=par Shl 25 Shr 31
pc+3
cycle+4
Case 72'48 PHA
writemem(S,A)
S+1
pc+1
cycle+3
Case 76'4C JMP absolute
pc=readmem(pc+2) Shl 8 + readmem(pc+1)
cycle+3
Case 96'RTS
S=(256+S-1) Mod 256
tS=(256+S-1) Mod 256
pc=readmem(256+S)+1+readmem(256+tS)Shl 8
S=tS
cycle+6
Color cbmagenta
Case 101'65 ADC Zero Page
A=A+readmem(readmem(pc+1))+c
If A>255 Then C=1:A= A Mod 256 Else C=0
If A=0 Then Z=1 Else Z=0:N=A Shr 7'FLAGIT
pc+2
cycle+3
Case 104'68 PLA
S-1
A=readmem(S)
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+1
cycle+4
Case 105'69 ADC Immediate
A=A+readmem(pc+1)+c
If A>255 Then C=1:A= A Mod 256 Else C=0
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+2
cycle+3
Case 106'6A ROR
T=A Mod 2
A=A Shr 1 + C Shl 7
C=T
pc+1
cycle+2
Case 109'6D ADC Absolute
A=A+readmem(readmem(pc+2) Shl 8 + readmem(pc+1))+c
If A>255 Then C=1:A= A Mod 256 Else C=0
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+3
cycle+4
Case 120'78 SEI 1 -> I
I=1
pc+1
cycle+2
Case 132'84 STY Zero page
writemem(readmem(pc+1),Y)
'Print " STY "+readmem(pc+1)+" ("+readmem(readmem(pc+1))+")"
pc+2
cycle+3
Case 133'85 STA Zero Page
writemem(readmem(pc+1),A)
pc+2
cycle+3
Case 134'86 STX Zero Page
writemem(readmem(pc+1),X)
pc+2
cycle+3
Case 136'88 DEY Y - 1 -> Y
Y=(256+Y-1) Mod 256
pc+1
cycle+2
N=Y Shr 7
Z=(Y=0)
Case 138'8A TXA
A=X
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+1
cycle+2
Case 140'8C STY Absolute
writemem(readmem(pc+2) Shl 8 + readmem(pc+1),Y)
pc+3
cycle+4
Case 141'8D STA Absolute
writemem(readmem(pc+2) Shl 8 + readmem(pc+1),A)
pc+3
cycle+4
Case 142'8E STX Absolute
writemem(readmem(pc+2) Shl 8 + readmem(pc+1),X)
'Print " "+(readmem(pc+1) Shl 8 + readmem(pc+2))
pc+3
cycle+4
Case 145'91 STA (Indirect),Y
par=readmem(pc+1)
writemem((readmem(par+1) Shl 8 + readmem(par) + Y),A)
'Print " STA"+"("+par+"),Y="+Y+" =STA "+(readmem(par+1) Shl 8 + readmem(par) + Y)
pc+2
cycle+6
Case 149'95 STA zero page, X
writemem((X+readmem(pc+1))Mod 256,A)
pc+2
cycle+4
Case 152'98 TYA
A=Y
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+1
cycle+2
Case 153'99 STA Absolute, Y
writemem(readmem(pc+2) Shl 8 + readmem(pc+1)+Y,A)
pc+3
cycle+5
Case 154'9A TXS X -> S
writemem(256+S,X)
S=(256+S+1) Mod 256
pc+1
cycle+2
Case 157'9D STA Absolute, X
writemem(readmem(pc+2) Shl 8 + readmem(pc+1)+X,A)
pc+3
cycle+5
Case 160'A0 LDY Immediate
Y=readmem(pc+1)
pc+2
cycle+2
N=Y Shr 7
Z=(Y=0)
Case 162'A2 LDX Immediate
X=readmem(pc+1)
pc+2
cycle+2
N=X Shr 7
Z=(X=0)
Case 164'A4 LDY Zero Page
Y=readmem(readmem(pc+1))
If Y=0 Then Z=1:N=0 Else Z=0:N=Y Shr 7'FLAGIT
pc+2
cycle+3
Case 165'A5 LDA Zero Page
A=readmem(readmem(pc+1))
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+2
cycle+3
Case 166'A6 LDX Zero Page
X=readmem(readmem(pc+1))
If X=0 Then Z=1:N=0 Else Z=0:N=X Shr 7'FLAGIT
pc+2
cycle+3
Case 168'A8 TAY
Y=A
If Y=0 Then Z=1 Else Z=0:N=Y Shr 7
pc+1
cycle+2
Case 169'A9 LDA Immediate
A=readmem(pc+1)
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+2
cycle+2
Case 170'AA TAX
X=A
If X=0 Then Z=1 Else Z=0:N=X Shr 7
pc+1
cycle+2
Case 173'AD LDA Absolute
A=readmem(readmem(pc+2) Shl 8 + readmem(pc+1))
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+3
cycle+4
Case 177'B1 LDA (Indirect),Y
par=readmem(pc+1)
p2=readmem(par)
A=readmem(readmem(par+1) Shl 8 + p2 + Y)
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
If p2+Y>255 Then cycle=6 Else cycle=5
pc+2
Case 185'B9 LDA Absolute, Y
addr=readmem(pc+2) Shl 8 + readmem(pc+1)
A=readmem(addr+ Y)
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+3
cycle=cycle+4+((addr Shr 8) =((addr+Y)Shr 8) )
Case 189'BD LDA Absolute,X
addr=readmem(pc+2) Shl 8 + readmem(pc+1)
A=readmem(addr+ X)
If A=0 Then Z=1:N=0 Else Z=0:N=A Shr 7'FLAGIT
pc+3
cycle=cycle+4+((addr Shr 8) =((addr+X)Shr 8) )
Case 198'C6 DEC Zero-page
par=readmem(pc+1)
par2=(256+(readmem(par)-1)) Mod 256
writemem(par,par2)
'Print " DEC "+par+" ="+readmem(par)
pc+2
cycle+5
N=par2 Shr 7
Z=(par2=0)
Case 200'C8 DEY
X=(256+Y-1) Mod 256
pc+1
cycles+2
N=Y Shr 7
Z=(Y=0)
Case 201'C9 CMP
T=(256+A-readmem(pc+1)) Mod 256
C=(T>=0)
Z=(T=0)
N=T Shr 7
pc+2
cycle+2
Case 202'CA DEX X - 1 -> X
X=(256+X-1) Mod 256
pc+1
cycles+2
N=X Shr 7
Z=(X=0)
Case 208'D0 BNE Relative
If Z=0 Then
param=readmem(pc+1)
oldpc=pc
pc=pc+1+(param Mod 128)-((param>127)*127)
cycle=cycle+3+(RoundDown((oldpc-32768)/16384.0)<>RoundDown((pc-32768)/16384.0))'OPTIMOI!
Else
pc+2
cycle+2
EndIf
'Print "hyaa"
Case 216'D8 CLD 0 -> D(D-flagilla ei nesissä ole mitään käyttöä)
D=0
pc+1
cycle+2
Case 224'E0 CPX
T=(256+X-readmem(pc+1)) Mod 256
C=(T>=0)
Z=(T=0)
N=T Shr 7
pc+2
cycle+2
Case 230'E6 INC Zero Page
addr=readmem(pc+1)
writemem(addr,(readmem(addr)+1) Mod 256)
pc+2
cycle+5
Case 232'E8 INX
X=(X+1)*(X<>255)
If X=0 Then Z=1 Else Z=0:N=X Shr 7'FLAGIT
pc+1
cycle+2
Case 234'EA NOP
pc+1
cycle+2
Case 240'F0 BEQ
If Z=1 Then
param=readmem(pc+1)
oldpc=pc
pc=pc+1+(param Mod 128)-((param>127)*127)
cycle=cycle+3+(RoundDown((oldpc-32768)/16384.0)<>RoundDown((pc-32768)/16384.0))'OPTIMOI!
Else
pc+2
cycle+2
EndIf
Case 253'FD SBC Absolute,X
'addr=readmem(pc+2) Shl 8 + readmem(pc+1)
par1=readmem(pc+2)
par2=readmem(pc+1)
addr=par1 Shl 8 + par2 +X
A=A-readmem(addr)
If A<0 Then A=0:C=1 Else C=0
'owerflowi puuttuu
pc+3
cycle=cycle+4+par2+X>255
Color cbmagenta
Default'jos komentoa ei löytynyt
cycle+1
pc+1
Color 255,0,0
Print op+" "+Right(Hex(op),2)
'Wait 2000
EndSelect
'Print op+" "+Right(Hex(op),2)
Color 255,255,255
ppu()
'printstatus()
'If N>1 Or N<0 Then Wait 5000
'WaitKey
'If KeyDown(cbkeyp) Then printstatus()
'If Not KeyDown(cbkeyspace) Then WaitKey' Else Wait 1'50
//vblankin generointi
If cycle<4 Or cycle>(cycle2+29829) Or KeyDown(cbkeyv) Then cycle2=cycle:ppu_vblank=1:Print "vblank"':Wait 2000
Wend
Function readmem(address)//koko prossun muisti
If address=>32768 Then//ROM
Select mapper
Case 0'NOMAPPER
If rombanks=1 Then 'käsittely yhdellä rompankilla, se peilautuu ylä ja alapankkiin.
'Print (address-32768) Mod 16384
Return PeekByte(rom,(address-32768) Mod 16384)
ElseIf rombanks=2 'kahdella rompankilla ne näkyy peräkkäin(ei testattu)
'Print address-32768
Return PeekByte(rom,address-32768)
Else
MakeError rombanks+" pankkisia NOMAPPER-romeja ei ole"+chr(10)+"(an error that was impossible to occur has just occured.)"
EndIf
Default
If rombanks=1 Then 'nomapper
Return PeekByte(rom,(address-32768) Mod 16384)
Else
Return PeekByte(rom,address-32768)
EndIf
'MakeError mapper+"ei ole tuettu mapperi"
EndSelect
ElseIf address<8192 Then//RAM
Return PeekByte(ram,address)
ElseIf address=>8192 And address<16383 Then//Registers
Select address Mod 8
Case 2'$2002
ret=ppu_vblank Shl 7//eivalmis
ppu_vblank=0
Return ret
'Case 4'04 Sprite Memory Data
Case 7
Print "PPUIO luku"
Default
Print "hassu rekisteri(luku) "+Right(Hex(address),4)
Return 0
EndSelect
ElseIf address=>16384 And address<=16407 Then'toinen rekisterialue
Select address - 16384
Case 22
Print "Ohjain 1 luku"
Return Rand(255)
Case 23
Print "Ohjain 2 luku"
Default
Print "hassu rekisteri(luku) "+Right(Hex(address),4)
EndSelect
Else'jos osoitteella ei ole muistia niin toimitaan näin.
Print "hassu lukuosoite "+ address
Return address Shr 8
EndIf
EndFunction
Function writemem(address, arvo)//koko prossun muisti
If address<=8191 Then //RAM
PokeByte ram,address Mod 2048, arvo
ElseIf address=>8192 And address<16383 Then//Registers
Select address mod 8 'korvaa printit lukemisella
Case 0'$2000
Print "PPUCNT1 kirjoitus"
Case 1'$2001
Print "PPUCNT2 kirjoitus"
Case 3'$2003
Print "SPRADDR kirjoitus"
Case 4'$2004
Print "SPRIO kirjoitus"
Case 5'$2005
Print "BGSCROL kirjoitus"
Case 6'$2006
Print "PPUADDR kirjoitus"
ppuaddr_turn=((ppuaddr_turn+1)<2)
If ppuaddr_turn=1 Then
ppuaddr_addr=ppuaddr_addr Shl 24 Shr 24 + arvo Shl 8
'ppuaddr_addr0=arvo
Else
ppuaddr_addr=ppuaddr_addr Shr 8 Shl 8 +arvo
'ppuaddr_addr1=arvo
EndIf
Case 7'$2007
Print "PPUIO kirjoitus"
'huom:
'Write data into $2007. After each write, the address will auto-increment by 1, or 32 (if Bit #2 of $2000 is 1).
'muista mirroring tähän,lukuun ja näyttikseen kun mapperit lisääntyy
PokeByte ppuram, ppuaddr_addr, arvo
ppuaddr_addr+1
'Print ppuaddr_addr+ " "+arvo
'Wait 400
Default
Print "hassu rekisteri(kirjoitus) "+Right(Hex(address),4)
Return 0
EndSelect
ElseIf address=>16384 And address<=16407 Then
Select address - 16384
Case 22
Print "Ohjain 1 kirjoitus"
Case 23
Print "Ohjain 2 kirjoitus"
Default
Print "hassu rekisteri(kirjoitus) "+Right(Hex(address),4)
EndSelect
ElseIf address=>32768 Then//ROM
Select mapper
Case 0
Print "Ohjelmaromiin kirjoitus ei tee mitään tällä mapperilla"
Default
Print "tuntematon mapperi tekee jotain"
EndSelect
Else'jos osoitteella ei ole muistia niin toimitaan näin.
Print "hassu kirjoitusosoite "+address
EndIf
EndFunction
Function ppu()
//kuva kerrallaan piirto, korjataan scanline/pikseli kerrallaan piirrolla.
'SetWindow ""+cycle
If cycle>1789773/60 Or KeyDown(cbkeyv) Then
SetWindow (Timer()-aika)+" "+1000/60+"sta"
cycle=0
t=Timer()
Select rendermethod
Case 0
DrawToImage scr
Lock
For sy=0 To 239
For til=0 To 31
tilenumber=PeekByte(ppuram,8192+(RoundDown(sy/8.0)*32+til))+256
byte1=PeekByte(vrom,tilenumber*16+(sy Mod 8)-1)
byte2=PeekByte(vrom,tilenumber*16+(sy Mod 8)+7)
pal=PeekByte(ppuram,9152+RoundDown(sy/32.0)*8+RoundDown(til/4.0)) Shl (24+2*(RoundDown(til/2+1)Mod 2)+4*(RoundDown(sy/16+1) Mod 2)) Shr 30
For sx=0 To 7
PutPixel2 til*8+sx,sy,palette(PeekByte(ppuram,16128+(((byte1 Shr (7-sx)) Mod 2))+((((byte2 Shr (7-sx)) Mod 2))Shl 1)+(pal) Shl 2))
'SetWindow ""+(PeekByte(ppuram,16128+(byte1 Shr (7-sx)) Mod 2+((byte2 Shr (7-sx)) Mod 2)Shl 1 +pal Shl 2))+" "+((byte1 Shr (7-sx)) Mod 2+((byte2 Shr (7-sx)) Mod 2)Shl 1 +pal Shl 2)+ " " +pal
Next sx
Next til
Next sy
Unlock
DrawToScreen
Case 1
Randomize 12342565
For l=4 To 7'tilet
DrawToImage renderlayers(l)
Color cbblack
Box 0,0,256,240
j=l-4
For i=0 To 959
'DrawImageBox maskedtiles(j),i*8 Mod 256,RoundDown(i/32.0)*8,(PeekByte(ppuram,8192+i)+256)Shl 3,0,8,8
CopyBox (PeekByte(ppuram,8192+i)+256)Shl 3,0,8,8,(i Shl 3) Mod 256,RoundDown(i/32.0)Shl 3,Image(maskedtiles(j)),Image(renderlayers(l))
Next i
Next l
For l=0 To 3'tausta
DrawToImage renderlayers(l)
Color cbblack
Box 0,0,256,240
For i=0 To 63
pal=PeekByte(ppuram,9152+i)
Color 0,0,palette(PeekByte(ppuram,16128+((pal Shl 30) Shr 28)+l))
Box i*32 Mod 256, RoundDown(i/8.0)*32,16,16
Color 0,0,palette(PeekByte(ppuram,16128+((pal Shl 28) Shr 30)Shl 2+l))
Box (i*32+16) Mod 256, RoundDown(i/8.0)*32,16,16
Color 0,0,palette(PeekByte(ppuram,16128+((pal Shl 26) Shr 30)Shl 2+l))
Box i*32 Mod 256, RoundDown(i/8.0)*32+16,16,16
Color 0,0,palette(PeekByte(ppuram,16128+((pal Shl 24) Shr 30)Shl 2+l))
Box (i*32+16) Mod 256, RoundDown(i/8.0)*32+16,16,16
Next i
DrawImage renderlayers(l+4),0,0
DrawToScreen
Next l
DrawToImage scr
Color cbblack
Box 0,0,256,240
DrawImage renderlayers(0),0,0
DrawImage renderlayers(1),0,0
DrawImage renderlayers(2),0,0
DrawImage renderlayers(3),0,0
DrawToScreen
EndSelect
Color cbblack
Box 200,0,256,240
DrawImage scr,200,0' kaksi piirtoa koska print pistää koordinaatit sekaisin
'DrawImage scr,200,150
DrawScreen OFF
SetWindow ""+(Timer()-t)
aika=Timer()
EndIf
EndFunction
Function printstatus()
Print "A:"+A+" X:"+X+" Y:"+Y+" N:"+N+" V:"+V+" I:"+I+" Z:"+Z+" C:"+C+" S(tack):"+S+" PC:"+Right(Hex(PC),4)+" ppuaddr:"+ppuaddr_addr
EndFunction
//relative (param Mod 128)-((param>127)*127)
//absolute readmem(readmem(pc+2) Shl 8 + readmem(pc+1))
Code: Select all
'If cycle<4 Or cycle>(cycle2+1789773/60) Or KeyDown(cbkeyv) Then cycle2=cycle:ppu_vblank=1:Print "vblank"':Wait 2000
If cycle>29829 Or KeyDown(cbkeyv) Then cycle=0:ppu_vblank=1:Print "vblank"':Wait 2000
Code: Select all
If cycle<4 Or cycle>(cycle2+29829) Or KeyDown(cbkeyv) Then cycle2=cycle:ppu_vblank=1:Print "vblank"':Wait 2000
Ainiin. kuvia tietysti:
Saa kysellä jos jokin on epäselvää.
Romit: http://nesdev.parodius.com/zelda.zip http://kahesi.net/nesomatohjelmat.php#fighter Nämä ovat tietysti laillisia. En kannata warea. En ole montaa itse testannut, joten muutkin saattavat toimia jotenkin.
Linkkejä muihin tietolähteisiin:
http://www.emutalk.net/threads/25700-Nes 'oikein hyvä.
http://nesdev.parodius.com/ 'paljon tavaraa
http://wiki.nesdev.com/w/index.php/Nesdev_Wiki 'wikimuodossa paljon tietoa.
ps. Jeesus elää ja on ainoa tie taivaaseen ja jumalan yhteyteen. Jeesus pelastaa ne jotka tunnustavat hänet herraksensa ja tekevät hänen tahtonsa mukaan. Jeesus on Jumalan poika ja kuoli ihmisten syntien tähden, jotta jokainen voisi pelastua hänen kauttansa. Jumala on täydellisen hyvä eikä voi sietää mitään pahaa. Jeesuksen täydellisen uhrin kautta ihminen voi saada sovituksen ja tulla taas jumalan silmissä täydelliseksi. Jumalan tahto on, että jokainen pelastuisi. Jumala loi kuitenkin ihmiselle vapaan tahdon, joten valinta pelastuksen hyväksymisestä on jokaisella itsellä.
"kaikki ovat tehneet syntiä ja ovat vailla Jumalan kirkkautta." Room. 3:23