diff --git a/README.md b/README.md index 91b6b20..b0619a3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# FSPDS + + +# FSPDS V0.1 ## Flipnote Studio Player for Nintendo DS @@ -10,7 +12,7 @@ This simple ROM allows you to play flipnotes on Nintendo DS (Lite) using your fl - Create a new folder named ```/flipnotes``` at the root of your SD card - Copy your favorite flipnotes to ```/flipnotes``` directory -- Copy ```FSPDS.nds``` anywhere on your SD card +- Copy ```FSPDS.nds``` anywhere to your SD card - Insert your flashcard into the DS, then turn on the console - Launch ```FSPDS.nds``` - Select a file from the menu using the D-pad @@ -18,14 +20,18 @@ This simple ROM allows you to play flipnotes on Nintendo DS (Lite) using your fl - Use ```L```/```R``` buttons to navigate through bottom-screen tabs ## Screenshots +

+ ROM in action: show thumbnail +
+ Thumbnail of one of the sample flipnotes (Cat on a skateboard) displayed by FSPDS +

-![ROM in action: show thumbnail](https://github.com/NotImplementedLife/Resources/blob/master/FSPDS/images/ss01.png "Show thumbnail") - -*Thumbnail of one of the sample flipnotes (Cat on a skateboard) displayed by FSPDS* -![ROM in action: playing a flipnote](https://github.com/NotImplementedLife/Resources/blob/master/FSPDS/images/ss02.png "Playing a flipnote") - -*Capture of "eBay Song" flipnote by RANDOM☆GUY played by FSPDS* +

+ ROM in action: show thumbnail +
+ Capture of "eBay Song" flipnote by RANDOM☆GUY played by FSPDS +

## Bugs/Limitations @@ -33,6 +39,13 @@ This simple ROM allows you to play flipnotes on Nintendo DS (Lite) using your fl - Don't place more than 1024 files in ```/flipnotes``` directory, otherwise FSPDS won't be capable to see them all (also intentional behavior, probably will be changed in the future) - No sound currently -- It's possible to encounter some graphical glitches (they'll be fixed soon) +- ~~It's possible to encounter some graphical glitches (they'll be fixed soon)~~ [*Hopefully not!*] + +### Tested on DeSmuMe emulator and R4(i) flashcards + +## Credits -- Tested on R4(i) flashcards +- Guys from [DSiBrew](https://dsibrew.org/wiki/Flipnote_Files/PPM "PPM Format") and [Flipnote Collective](https://github.com/Flipnote-Collective/flipnote-studio-docs/wiki/PPM-format "PPM Format") for their awesome documentation on .PPM file format. +- [devkitPro](https://github.com/devkitPro) for their compiler, ndslib and examples +- The authors behind [GodMode9i](https://github.com/DS-Homebrew/GodMode9i "GodMode9i") for the idea of overwriting character fonts +- [Drenn1/GameYob](https://github.com/Drenn1/GameYob "Drenn1/GameYob") for helping me figure out how to work with makefiles. \ No newline at end of file diff --git a/README_Resources/ss01.png b/README_Resources/ss01.png new file mode 100644 index 0000000..b20a0e5 Binary files /dev/null and b/README_Resources/ss01.png differ diff --git a/README_Resources/ss02.png b/README_Resources/ss02.png new file mode 100644 index 0000000..6ad453c Binary files /dev/null and b/README_Resources/ss02.png differ diff --git a/arm9/Makefile b/arm9/Makefile index 3e8a477..2eab6bf 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -31,7 +31,7 @@ CFLAGS := -Wall -Wno-psabi -DNO_VIZ \ -march=armv5te -mtune=arm946e-s -fomit-frame-pointer\ -ffast-math -O3 $(ARCH) $(DEBUG) \ -DVERSION_STRING=\"`git describe --always --abbrev=4`\"\ - -DC_IO_FUNCTIONS -DNIFI \ + -DC_IO_FUNCTIONS \ -include "nds/ndstypes.h" CFLAGS += $(INCLUDE) -DARM9 -DDS diff --git a/arm9/include/console.h b/arm9/include/console.h index e93c8a0..471aaf8 100644 --- a/arm9/include/console.h +++ b/arm9/include/console.h @@ -31,6 +31,8 @@ void c_writeFrame() iprintf("\025"); for(int i=1;i<31;i++) iprintf("\004"); iprintf("\023"); + c_goto(23,4); + iprintf("V0.1 - Created by N\032I\032L"); //for(i=32;i--;) iprintf("\x02"); } @@ -98,7 +100,7 @@ static inline void initConsole() dmaFillWords(0xFFFFF000, (void*)0x62002B4, 4); dmaFillWords(0x00000000, (void*)0x62002B8, 8); - // Char #26 + // Char #26 - Play Triangle Up 1 void* address=(void*)0x62002C0; u32 word=0x000000FF; for(u8 i=3;i--;) @@ -109,7 +111,7 @@ static inline void initConsole() } dmaFillWords(word, address,20); - // Char #27 + // Char #27 - Play Triangle Up 2 address=(void*)0x62002E0; word=0x00000000; dmaFillWords(word,address,16); @@ -122,7 +124,7 @@ static inline void initConsole() address+=0x4; } - // Char #28 + // Char #28 - Play Triangle Bottom 1 address=(void*)0x6200300; word=0xFFFFFFFF; @@ -136,7 +138,7 @@ static inline void initConsole() address+=0x4; } - // Char #29 + // Char #29 - Play Triangle Bottom 2 address=(void*)0x6200320; word=0xFFFFFFFF; for(int i=4;i--;) @@ -147,6 +149,12 @@ static inline void initConsole() } dmaFillWords(word,address,16); + // Char #30 - Bullet + dmaFillWords(0x00000000, (void*)0x6200340, 8); + dmaFillWords(0x000FF000, (void*)0x6200348, 4); + dmaFillWords(0x00FFFF00, (void*)0x620034C, 8); + dmaFillWords(0x000FF000, (void*)0x6200354, 4); + dmaFillWords(0x00000000, (void*)0x6200358, 8); BG_PALETTE[ 0]=BG_PALETTE_SUB[ 0]=0x7FFF; // used when clear screen BG_PALETTE[ 15]=BG_PALETTE_SUB[ 15]=0x7FFF; // used by \x1b[30m @@ -183,7 +191,7 @@ void c_loadingBox() for(int i=10;i<13;i++) { c_goto(i,10); - for(int j=0;j<10;j++) iprintf(" "); + for(int j=0;j<12;j++) iprintf(" "); } consoleSelect(&consoleFG); iprintf("\x1b[39m"); diff --git a/arm9/include/player.h b/arm9/include/player.h index ddf6a04..99c7c74 100644 --- a/arm9/include/player.h +++ b/arm9/include/player.h @@ -52,16 +52,12 @@ void setPixelThumbnail(u8 x,u8 y,u16 c) { int xx=32+x, yy=48+2*y; mainGFX[128*(yy++)+xx]=c+(c<<8); - //mainGFX[128*(yy++)+xx]=c; mainGFX[128*yy+xx]=c+(c<<8); - //mainGFX[128*yy+xx]=c; } void playerClear() { - for(int x=0;x<256;x++) - for(int y=0;y<192;y++) - mainGFX[128*y+x]=0x1010; + memset(mainGFX,0x10,256*192); } void displayThumbnail() @@ -88,13 +84,9 @@ void displayThumbnail() } } } - //bgSetScroll(bgMain, -50, -50); - //bgUpdate(); playerSwapBuffers(); } -//u8 *layer; - struct decodeType { int i, k; @@ -103,90 +95,22 @@ struct decodeType bool diffing; }; -void buildLayer(struct decodeType* dt) -{ - /*if(dt->diffing) - { - //memcpy(layerA,layer,LAYER_SIZE); - dmaCopy(layer,layerA,LAYER_SIZE); - }*/ - for(u16 y=0;y<192;y++) - { - u16 yy=y<<5; - switch((ppm_AnimationData[dt->i+(y>>2)]>>((y&0x3)<<1)) & 0x3) - { - case 0: - { - //dmaFillWords((u32)0,(u8*)(layer+yy),8); - //yy+=32; - for(u8 _i=0;_i<32;_i++) - layer1[yy++]=0; - break; - } - case 3: - { - for(u16 c=32;c--;) - { - layer1[yy++] = ppm_AnimationData[dt->k++]; - } - break; - } - case 2: - { - // Fill line - for(int _i=0; _i<32; layer1[yy+(_i++)]=0xFF); - goto __DECODETYPE_1_2__; - } - - case 1: - { - // Clear line - for(int _i=0; _i<32; layer1[yy+(_i++)]=0); - - __DECODETYPE_1_2__: ; - - u32 bytes=ppm_AnimationData[dt->k++]<<24; - bytes|=ppm_AnimationData[dt->k++]<<16; - bytes|=ppm_AnimationData[dt->k++]<<8; - bytes|=ppm_AnimationData[dt->k++]; - for(;bytes;) - { - if(bytes & 0x80000000) - { - layer1[yy] = ppm_AnimationData[dt->k++]; - } - bytes<<=1; - yy++; - } - break; - } - } - } - /*if(dt->diffing) - { - for(u16 y=0;y<96;y++) - { - if(ytY) continue; - if(y-dt->tY>=192) break; - u16 BL=y<<5,BA=(y-dt->tY)<<5; - for(u8 c=0;c<32;c++) - for(u8 b=0;b<8;b++) - { - u8 x=8*c+b; - if(xtX) continue; - if(x-dt->tX>=256) break; - u8 dx=(x-dt->tX); - layer[BL+c]^= ((bool)(layerA[BA+(dx>>3)]&(1<<(dx&0x7))))< The maximum flipnote framerate is 30fps. */ +/** > 1/30 = 1/60 + 1/60, hence, in 1/30 seconds two vBlanks happen */ +/** > Frame rendering is too expensive to be executed in < 1/60 = 16.6ms */ +/** > So, I'll split the frame rendering into two separate subtasks, each */ +/** > to be executed in its own vBlank. */ +/** > */ +/** > The procedure is as follows: */ +/** > vBlank 0 - parse animation data and do frame diffing up to ~68% of */ +/** > the lines */ +/** > vBlank 1 - finish frame diffing and write graphics into memory */ +/** > */ +/** > If the playback speed is smaller (e.g. 12fps = 5 vBlanks), then the */ +/** > code does nothing during the additional vBlanks. */ +/** > The real rendering speed is adjusted using the {counter} in main() */ -/* [ vBlank Op ] Because maximum flipnote frame rate is 30 fps (so a frame each 2 vBlanks) - I decided render each of the two layers on a separate vBlank - Hope this will minimize the chances of missing vBlanks, which could cause - synchronization troubles -*/ void playerNextFrameVBlank0(struct decodeType* dt) { @@ -203,8 +127,6 @@ void playerNextFrameVBlank0(struct decodeType* dt) if(dt->diffing) { - //dmaCopy(layer1,layerA,LAYER_SIZE); - //dmaCopy(layer2,layerB,LAYER_SIZE); memcpy(layerA,layer1,LAYER_SIZE); memcpy(layerB,layer2,LAYER_SIZE); } @@ -221,10 +143,6 @@ void playerNextFrameVBlank0(struct decodeType* dt) } case 3: { - /*for(u16 c=32;c--;) - { - layer1[yy++] = ppm_AnimationData[dt->k++]; - }*/ memcpy(layer1+yy,ppm_AnimationData+dt->k,32); yy+=32; dt->k+=32; @@ -234,15 +152,13 @@ void playerNextFrameVBlank0(struct decodeType* dt) { // Fill line memset(layer1+yy,0xFF,32); - //for(int _i=0; _i<32; layer1[yy+(_i++)]=0xFF); goto __DECODETYPE_1_2__; } case 1: { // Clear line - //memset(layer1+yy,0x00,32); - for(int _i=0; _i<32; layer1[yy+(_i++)]=0); + memset(layer1+yy,0x00,32); __DECODETYPE_1_2__: ; @@ -271,10 +187,6 @@ void playerNextFrameVBlank0(struct decodeType* dt) { case 0: { - //dmaFillWords((u32)0,(u8*)(layer+yy),8); - //yy+=32; - //for(u8 _i=0;_i<32;_i++) - // layer2[yy++]=0; memset(layer2+yy,0,32); yy+=32; break; @@ -290,7 +202,6 @@ void playerNextFrameVBlank0(struct decodeType* dt) { // Fill line memset(layer2+yy,0xFF,32); - //for(int _i=0; _i<32; layer2[yy+(_i++)]=0xFF); goto __DECODETYPE_1_2__L2; } @@ -298,7 +209,6 @@ void playerNextFrameVBlank0(struct decodeType* dt) { // Clear line memset(layer2+yy,0x00,32); - //for(int _i=0; _i<32; layer2[yy+(_i++)]=0); __DECODETYPE_1_2__L2: ; @@ -323,15 +233,14 @@ void playerNextFrameVBlank0(struct decodeType* dt) { u16 ld0 = (dt->tX>=0) ? (dt->tX>>3) : 0; u16 pi0 = (dt->tX>=0) ? 0 : (-dt->tX)>>3; - u8 del = dt->tX>=0 ? (dt->tX & 7) : ((126+dt->tX)&7); + u8 del = dt->tX>=0 ? (dt->tX & 7) : (((u8)(dt->tX))&7); + u8 ndel=8-del; u8 alpha = (1<<(8-del))-1; u8 nalpha = ~alpha; u16 pi=0,ld=0; - //c_goto(5,5); - //iprintf("%d %d %d %d ",del, ld0, pi0,nalpha); if(dt->tX>=0) { - for(u16 y=0;y<120;y++) + for(u16 y=0;y<130;y++) { if(ytY) continue; if(y-dt->tY>=192) break; @@ -341,8 +250,8 @@ void playerNextFrameVBlank0(struct decodeType* dt) layer2[ld++] ^= layerB[pi] & alpha; while((ld&31)<31) { - layer1[ld]^=(layerA[pi]&nalpha) | (layerA[pi+1]&alpha); - layer2[ld]^=(layerB[pi]&nalpha) | (layerB[pi+1]&alpha); + layer1[ld]^=((layerA[pi]&nalpha)>>ndel) | ((layerA[pi+1]&alpha)<>ndel) | ((layerB[pi+1]&alpha)<tY) continue; if(y-dt->tY>=192) break; @@ -359,8 +268,8 @@ void playerNextFrameVBlank0(struct decodeType* dt) pi=((y-dt->tY)<<5)+pi0; while((pi&31)<31) { - layer1[ld]^=(layerA[pi]&nalpha) | (layerA[pi+1]&alpha); - layer2[ld]^=(layerB[pi]&nalpha) | (layerB[pi+1]&alpha); + layer1[ld]^=((layerA[pi]&nalpha)>>ndel) | ((layerA[pi+1]&alpha)<>ndel) | ((layerB[pi+1]&alpha)<diffing) { - //c_goto(1,1); - //iprintf("%d ",frameTime[ppm_FramePlaybackSpeed]); u16 ld0 = (dt->tX>=0) ? (dt->tX>>3) : 0; - u16 pi0 = (dt->tX>=0) ? 0 : (-dt->tX)>>3; - u8 del = dt->tX>=0 ? (dt->tX & 7) : ((126+dt->tX)&7); + u16 pi0 = (dt->tX>=0) ? 0 : (((u8)(-dt->tX))>>3); + u8 del = (dt->tX>=0) ? (dt->tX & 7) : (((u8)(dt->tX))&7); + u8 ndel=8-del; u8 alpha = (1<<(8-del))-1; u8 nalpha = ~alpha; u16 pi=0,ld=0; if(dt->tX>=0) { - for(u16 y=120;y<192;y++) + for(u16 y=130;y<192;y++) { if(ytY) continue; if(y-dt->tY>=192) break; ld=(y<<5)+ld0; pi=((y-dt->tY)<<5)+pi0; - layer1[ld] ^= layerA[pi] & alpha; - layer2[ld++] ^= layerB[pi] & alpha; + layer1[ld] ^= (layerA[pi] & alpha)<>ndel) | ((layerA[pi+1]&alpha)<>ndel) | ((layerB[pi+1]&alpha)<>ndel) | ((layerA[pi+1]&alpha)<>ndel) | ((layerB[pi+1]&alpha)<tY) continue; if(y-dt->tY>=192) break; @@ -413,16 +321,18 @@ void playerNextFrameVBlank1(struct decodeType* dt) pi=((y-dt->tY)<<5)+pi0; while((pi&31)<31) { - layer1[ld]^=(layerA[pi]&nalpha) | (layerA[pi+1]&alpha); - layer2[ld]^=(layerB[pi]&nalpha) | (layerB[pi+1]&alpha); + layer1[ld]^=((layerA[pi]&nalpha)>>ndel) | ((layerA[pi+1]&alpha)<>ndel) | ((layerB[pi+1]&alpha)<>ndel; + layer2[ld] ^= (layerB[pi] & nalpha)>>ndel; } } } + // prepare to draw frame + u16 paperColor = (dt->first_byte_header & 1); u16 layer1Color = (dt->first_byte_header>>1) & 0x3; if(layer1Color<2) @@ -438,12 +348,10 @@ void playerNextFrameVBlank1(struct decodeType* dt) u16 p=0,val=0; for(u16 y=0,yy=0;y<192;y++) { - //u16 yy=(y<<5); for(u8 c=32;c--;yy++) { for(u8 b=0;b<8;b++) { - //u16 p=(yy<<2)+(b>>1); if(layer1[yy]&(1<