Forums

Sega Master System / Mark III / Game Gear
SG-1000 / SC-3000 / SF-7000 / OMV
Home - Forums - Games - Scans - Maps - Cheats - Credits
Music - Videos - Development - Hacks - Translations - Homebrew

View topic - devkitSMS - develop your homebrew in C

Reply to topic Goto page Previous  1, 2, 3, 4, 5, 6, 7 ... 14, 15, 16  Next
Author Message
  • Joined: 07 Oct 2015
  • Posts: 114
Reply with quote
Post Posted: Fri Dec 18, 2015 10:08 am
Kagesan wrote
na_th_an wrote
I will need to use 8 patterns per main player (there are two) and change the pattern data each frame. That means fetching and uploading 512 bytes per frame.
Will I have enough time during VBlank for the update?

If you find you're running out of time, you can always try to reduce the colours of your player sprites. I did this with the Bruce Lee sprite when I needed to get rid of a few cycles. My sprite originally used only 9 colours, so turning it into 3bpp tiles with 8 wasn't complicated and it sped up the constant refreshing of the tiles considerably.

More information on the method here.


That's quite interesting. Taking in account that the original sprites only have 3 colours, 8 seems quite a good compromise and you save a byte per pattern row. Clever.

I'll be just updating 512 bytes in the VBlank time, and I'll be preparing everything I need right before so everything is set up and ready when the VBlank begins.

I have to do some tests, but I think I will have enough time.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Dec 18, 2015 10:33 am
na_th_an wrote
That's quite interesting. Taking in account that the original sprites only have 3 colours, 8 seems quite a good compromise and you save a byte per pattern row. Clever.


3 colors? Use 2bpp tiles, then! (in case you really need to!)
  View user's profile Send private message Visit poster's website
  • Joined: 07 Oct 2015
  • Posts: 114
Reply with quote
Post Posted: Fri Dec 18, 2015 10:36 am
sverx wrote
na_th_an wrote
That's quite interesting. Taking in account that the original sprites only have 3 colours, 8 seems quite a good compromise and you save a byte per pattern row. Clever.


3 colors? Use 2bpp tiles, then! (in case you really need to!)


That would make the game look "too NES". Nah, the SMS is my favourite console and I want to make it justice ;)
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Dec 18, 2015 10:52 am
So use how many colors you can / you need.
Since we've got only a single sprite palette, you need to create one that has each color you need, and sometimes clever thinking and creativity is needed to avoid wasting this scarce resource.

OT: are we working exactly on the same thing? I've just added tile streaming...

void main (void) {
  SMS_VDPturnOnFeature(VDPFEATURE_LEFTCOLBLANK);
  SMS_setSpriteMode(SPRITEMODE_TALL);

  loadAssets();
  loadLevel(level1);
  prepareBG();
 
  SMS_displayOn();

  for (;;) {
    SMS_waitForVBlank();
    UNSAFE_SMS_copySpritestoSAT();
    if (someboolean)
      changeTiles();


Also, I find useful to place two CPU meters on screen, so that I can check if I'm not doing something very wrong:

    // BETA: CPU meter
    temp=SMS_getVCount();
    SMS_addSprite(248,temp,CPU_METER_TILE);
    if ((temp<=210) && (temp>max))
      max=temp;
    SMS_addSprite(248,max,CPU_METER_TILE);
    // end CPU meter

    SMS_finalizeSprites();
  }  // end for
  View user's profile Send private message Visit poster's website
  • Joined: 07 Oct 2015
  • Posts: 114
Reply with quote
Post Posted: Fri Dec 18, 2015 11:29 am
That's exactly what I need to do, but integrating my out-of-order sprites. In pseudo code (out of my head):

{
   (game logic)
   
   SMS_initSprites ();
   Add metasprite for 1UP, using patterns 0-5
   Add metasprite for 2UP, using patterns 6-11
   Add the rest of the metasprites, out of order
   SMS_finalizeSprites ();

   Calculate address of 1UP patterns data for current cell
   Calculate address of 2UP patterns data for current cell

   SMS_waitForVBlank ();
   UNSAFE_SMS_copySpritestoSAT ();
   Copy 6 patterns (192 bytes) to VRAM 0-5 to update 1UP current cell
   Copy 6 patterns (192 bytes) to VRAM 6-11 to  update 2UP current cell
}


I think that should work. For "metasprite" I mean several linked sprites with relative coordinates to an origin.

EDIT:

Now that I think of it, maybe a SMS_initSprites (unsigned char offset); would come in handy if a number of sprites at the beginning of the SAT doesn't need to be updated? This should skip them by setting SpriteNextFree to offset. I don't know if this will be useful, though (not for me: the pattern assignations for sprites won't change, but their coordenates will!).
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Dec 18, 2015 12:31 pm
na_th_an wrote
Now that I think of it, maybe a SMS_initSprites (unsigned char offset); would come in handy if a number of sprites at the beginning of the SAT doesn't need to be updated? This should skip them by setting SpriteNextFree to offset. I don't know if this will be useful, though (not for me: the pattern assignations for sprites won't change, but their coordenates will!).


Well, lately I've been thinking about adding a reserveSprite/updateSprite strategy, in a similar fashion on what I did with GBAdv library. I'm still thinking about that and, after all, I still haven't had any need or request. Another approach I've been thinking from a while is having freezeSprites/unfreezeSprites functions which would change the way initSprites works, which would add the functionality you just described.
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14688
  • Location: London
Reply with quote
Post Posted: Fri Dec 18, 2015 8:24 pm
It's also worth noting that in return for no hardware sprite flipping support, you can take the opportunity to have the flipped sprites be stored in ROM and drawn flipped with correct asymmetry - for example, the player may have a gun in their right hand which means different display when walking left and right. ROM space is very cheap these days...
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Mon Dec 21, 2015 2:56 pm
Alcoholics Anonymous wrote
What happens is the peepholer is unable to cross an inline asm boundary to determine if registers are live. So in your example the the peepholer is unable to determine if iy is dead or live in order to make the substitution and it's unable to determine if "ld l,0" is a dead load because it can't see the end of the function.


I'm testing SDCC --peep-asm switch, as it was suggested to me at their forum.
I'm comparing the two outputs side-by-side and it seems the output is good, so far. For instance this is SMS_setBGScrollX() using the --peep-asm switch:

;SMSlib.c:231: void SMS_setBGScrollX (int scrollX) {
;   ---------------------------------
; Function SMS_setBGScrollX
; ---------------------------------
_SMS_setBGScrollX::
;SMSlib.c:232: SMS_write_to_VDPRegister(0x08,LO(scrollX));
   di
   ld   hl, #2+0
   add   hl, sp
   ld   a, (hl)
   out   (_VDPControlPort),a
   ld   a,#0x88
   out   (_VDPControlPort),a
   ei
   ret


it's now using hl instead of iy, which is good, and overall seems quite like handwritten code, even if in that case I would have placed DI right before the first OUT, but that's a detail.
  View user's profile Send private message Visit poster's website
  • Joined: 17 Nov 2015
  • Posts: 97
  • Location: Canada
Reply with quote
Post Posted: Mon Dec 21, 2015 7:07 pm
sverx wrote

I'm testing SDCC --peep-asm switch,


You have to be careful to format your assembly exactly the same way as the compiler. If you are missing tabs, have extra whitespace, are missing '#' for constants, etc, the peepholer cannot understand your asm. At the first instruction that's not formatted correctly the peepholer will decide all registers are live. This limits "--peep-asm"'s usefulness somewhat. It's hard to get 'di' and 'ei' wrong though :)

Most of the time people don't want the peepholer changing their asm; one good reason for that is it will change timing. So it's probably ok to apply in an isolated manner, like if you compile a library so that it doesn't affect user code.

Anyway that's my two cents :)
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Dec 22, 2015 10:23 am
Alcoholics Anonymous wrote
Most of the time people don't want the peepholer changing their asm; one good reason for that is it will change timing.


That was my fear too. Fortunately, it seems it doesn't change anything in my handwritten asm code, just changes the code that's generated by SDCC.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Dec 22, 2015 12:09 pm
It turns out I can even move that DI closer to the OUT instruction, if I rewrite the macro as:
#define SMS_write_to_VDPRegister(VDPReg,value)    { unsigned char v=(value); DISABLE_INTERRUPTS; VDPControlPort=v; VDPControlPort=(VDPReg)|0x80; ENABLE_INTERRUPTS; }

so to have the load from memory in a separate instruction.

Unfortunately to make the peephole optimizer work correctly I need one more rule (I took rule 11 and made a copy which has DI in between):
replace restart {
   ld   %1, %2 (%3)
   di
   ld   %4, %1
} by {
   ; peephole rule 11-with_DI loaded %2 (%3) into %4 directly instead of going through %1.
   ld   %4, %2 (%3)
   di
} if canAssign(%4 %2 %3), notVolatile(%1), notUsed(%1)


The generated output is finally perfect:
;SMSlib.c:207: void SMS_setBGScrollX (int scrollX) {
;   ---------------------------------
; Function SMS_setBGScrollX
; ---------------------------------
_SMS_setBGScrollX::
;SMSlib.c:208: SMS_write_to_VDPRegister(0x08,LO(scrollX));
   ld   hl, #2+0
   add   hl, sp
   ld   a, (hl)
   di
   out   (_VDPControlPort),a
   ld   a,#0x88
   out   (_VDPControlPort),a
   ei
   ret


Of course I'll run a few tests before pushing this change to the public repository...
  View user's profile Send private message Visit poster's website
  • Joined: 09 Jan 2012
  • Posts: 67
  • Location: Germany
Reply with quote
Post Posted: Tue Jan 05, 2016 4:34 pm
Hello again,
i'm trying to understand how some basic functions works so i stripped down sms_rogue to get a minimal, working Hello World program. What i don't understand is how i fill the tile/tilesmap?

Currently my code looks like:
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "../devkitSMS/SMSlib.h"
#include "gfx.h"

void putchar(char c) {
   SMS_setTile(c - 32);
}

void title_screen(char* text) {
    SMS_setNextTileatXY(6, 5);
    puts(text);
    SMS_displayOn();
}


void load_font (void) {
    unsigned char i, j;
    unsigned char buffer[32], *o, *d;

   o = font_fnt;
   for(i = 0; i != 96; i++) {
      d = buffer;
      for(j = 0; j != 8; j++) {
         *d = *o; d++;
         *d = ~(*o);   d++;
         *d = 0;   d++;
         *d = 0;   d++;
            o++;
      }
      SMS_loadTiles(buffer, i, 32);
   }
}

void main(void) {
    load_font();
    SMS_setBGPaletteColor(01, 0x3f);   
    title_screen("Hello World");
    while(1);
}

Especially what is the meaning of the 3 bytes after the font character? Why is the buffer 32bytes long? And the last question, why is an implicit dependency between puts and putchar?
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14688
  • Location: London
Reply with quote
Post Posted: Tue Jan 05, 2016 7:39 pm
The SMS graphics format is a bit odd, separated biplanes with eight pixels per byte on each, making 32 bytes per tile. The code is padding out 1bpp data to this format in RAM and then pushing it to VRAM one tile at a time. Ordinarily you'd use one of the built in functions to load opaque 4bpp data directly, which would be much less code. Surely the project ought to have a Hello World?
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Jan 05, 2016 7:44 pm
Long story short: the tilemap (the image on screen) is made up of 32x28 tiles (only 24 lines visible - 192 pixel height), each tile is 8x8 pixel and needs to be stored into VRAM.
In the sample code, he's creating 96 tiles starting from some data (probably a 1bpp character set), and he's using that for writing "Hello World" to screen.
Using

SMS_setNextTileatXY(6, 5);

he's setting the 'cursor' to that location, then he uses stdio function puts() which, in turns, call the defined putchar() for each char the function wants to output on the screen.

Buffer is 32 bytes because that's the size of each tile, in SMS (in Mode 4) each tile is 8x8 and 4bpp = 32 bytes.

edit: of course Maxim was faster ;)
  View user's profile Send private message Visit poster's website
  • Joined: 09 Jan 2012
  • Posts: 67
  • Location: Germany
Reply with quote
Post Posted: Wed Jan 06, 2016 10:09 pm
Thank you both.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Jan 07, 2016 9:59 am
I'm working on a bigger and meaner devkitSMS/SMSlib... ahem, I meant smaller and faster, of course :)

First, I'm creating a real library, that is a SMSlib.lib file. It will contain several modules (.rel object files) so that SDCC will link only the modules needed: for instance if you don't use sprite clipping functions, these won't end up wasting space in ROM, saving you few bytes of RAM too.

I'm also doing quite a big work on making the functions compile to smaller and faster ASM code and, when appropriate, I'm switching to the available fastcall calling convention. This means less pushing values on the stack on function call.

I'll deprecate a couple of functions too, but I'll prepare macros to make that transition seamless.

Finally, Genesis/MegaDrive Pad support functions (which are under a conditional define) won't be active by default, exactly the opposite of what is now. I changed my idea, 'cause I guess the default option should be the one that gives less overhead... also, I see that in very few cases one wants to support additional buttons.

Feedback/comments/hints/additions welcome :)
  View user's profile Send private message Visit poster's website
  • Joined: 29 Mar 2012
  • Posts: 879
  • Location: Spain
Reply with quote
Post Posted: Fri Jan 08, 2016 7:15 am
I'm waiting to see it working! i want to give a bit of work to Gaudream with the improved library! :-D
  View user's profile Send private message
  • Joined: 07 Oct 2015
  • Posts: 114
Reply with quote
Post Posted: Fri Jan 08, 2016 10:34 am
Last edited by na_th_an on Fri Jan 08, 2016 10:44 am; edited 1 time in total
That's awesome news!

I have a suggestion, by the way.

While working on my SG1000 game I found myself struggling to fit the game frame in the NTSC frame. I found that lots of time was spent in dealing with sprites:

- I had to push my metasprites into a list.
- The list was processed out of order, calling a metasprite renderer which
- Called the sprite creation function in the library which
- Put values in the SAT array which
- Got copied to VRAM each frame.

That's a lot of layers, I mean, functions which call functions using slow parameter pushing. Besides, the sprite creation functions contain checks which slow down the process even more.

Those checks make sure we don't surpass the sprite limit and that we don't create a sprite with Y = D0.

IMHO, those checks are not needed - I mean, they take precious time, and I think that in most cases the coder should care about such things himself. It's the coder's fault to send more than 64 sprites or to create sprites with Y = D0, and the coder should be who took care of situations himself, not the library.

Anyways, my suggestion has to be with all that stuff: is there any way to make the SAT arrays directly accesible? I mean, being able to write directly to the SAT arrays bypassing the sprite creation functions entirely.

I had to do that in my game. I just added new functions to the library, but I think it would be nicer if you could achieve the same without having to tamper with the library. In my case, I save tons of cycles processing my metasprite structures and writing to the SAT arrays directly rather than having to call SG_addSprite for each sprite in the metasprite and have to spend cycles in the function call and the checks.

Giving direct access to the SAT arrays would allow the coders to have fixed positions for certain elements. Imagine having N sprites for your main character fixed at the beginning of the SAT and you just have to update the coordinates.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Jan 08, 2016 10:43 am
A few suggestions about that layering: make that as simple as possible from the beginning, then you can improve performance while calling functions inlining, whenever possible. Especially if your A function is the only one calling the B function, then declare B inline. If B calls C and again is the only one (or there are just a few) calling it, declare C inline. No call=no overhead :)

Also, to skip library function completely and write into the RAM copy of SAT by your own, just declare the two externs in your code:

extern unsigned char SpriteTableY[MAXSPRITES];
extern unsigned char SpriteTableXN[MAXSPRITES*2];


I'm not suggesting this, though. But I understand that when you're struggling with CPU everything counts. The new library will be faster too, but those checks (sprite limit and Y != D0) will remain in the code, sorry. But you don't have to check that yourself in your code, of course.
  View user's profile Send private message Visit poster's website
  • Joined: 07 Oct 2015
  • Posts: 114
Reply with quote
Post Posted: Fri Jan 08, 2016 10:52 am
Last edited by na_th_an on Fri Jan 08, 2016 11:01 am; edited 2 times in total
The extern stuff will suffice, but again I don't understand why the library has to take care of the coder's potential mistakes. I mean, this is a low level language. It's not like BASIC that you get an error when you try to write past the upper bound of an array or jump to a non existing label. If I write 200 bytes to a 150 bytes array I will get a crash. That's why C is faster than BASIC and that's what "low level" is all about. You take care of your stuff the best way you see fit.

For example if you have all your sprites inside the screen at all times, you will never ever send a Y=$D0, so you are doing a check which will always fail for every sprite sent to the SAT. In such controlled case the check is completely unneeded. Or if you have a game which will always move a fixed N sprites. You will never surpass the limit. Checking for it for each sprite in each frame is a waste of time, IMHO.

If I'm coding a game and I notice that sometimes half of my sprites disappear I scratch my head and try to find the cause... Oh, it's cause this projectile reaches Y=$D0! I notice and I fix it. I don't need the library babysitting by bad code :D

Just my two cents :) I still think the quality of the library is awesome and I really like how everything is done.

*And* this issue is not really an issue as the sources are available and anybody can customize the library as they see fit, so it's just me bitching ;)
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Jan 08, 2016 10:53 am
For anyone interested: a still unfinished beta of the new library is attached. Should work with your current setup, but the coming release will probably require to update SDCC too.

To compile your code using this lib you should place that in SDCC lib/z80 folder and call the compiler (linker) this way:

sdcc -o your_program.ihx -mz80 --data-loc 0xC000 --no-std-crt0 ..\crt0\crt0_sms.rel your_program.rel SMSlib.lib other_rel.rel


(I still have to check if I can ship crt0_sms.rel into the library too...)
SMSlib.rar (7.99 KB)
some very beta of the new library

  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14688
  • Location: London
Reply with quote
Post Posted: Fri Jan 08, 2016 1:58 pm
Why not ship the sprite table external definitions in a header so people can opt in while you get to avoid a dependency on your variable names?
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Jan 08, 2016 2:18 pm
@Maxim: You want me to show everyone how poor is my understanding of English, right? ;)

@na_th_an: I understand your point of view, but I insist that overrunning an array you defined in your code isn't the same that having to know that placing a sprite at Y=$D0 will make all the next sprites disappear.
And C is an HLL anyway ;)
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Sun Jan 17, 2016 7:19 pm
sverx wrote
I'm working on ...


... but it's taking longer than planned :|

In the while, I started turning some of the internal functions to native z80 ASM, to ensure they'll always be as fast as possible, and a few functions will turn to macros to make it possible for the SDCC compiler to optimize as much as possible at compile time (for instance, calling SMS_setNextTileatXY() with constant values will no longer make the compiler push the constants on top of the stack and defer address calculation to runtime (costly!), but will be turned into a compile time calculation of address and flags that will be passed to the VDP access function thru fastcall). Much faster and smaller :)

Will keep you posted.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Jan 19, 2016 10:05 am
Updated. I hope you enjoy :)
Your previously working code should compile with no problems. Be sure to change the invocation for SDCC' linker as it's now linking a .lib file.
The generated output should be faster and smaller, as now many SMSlib functions you're not using in your code won't end there. If for some reason you find that your final code is slower and/or bigger, please let me know.
  View user's profile Send private message Visit poster's website
  • Joined: 07 Oct 2015
  • Posts: 114
Reply with quote
Post Posted: Tue Jan 19, 2016 10:55 am
That's great news. Not into SMS dev proper yet, but will soon. I will keep you posted.

Thanks :)
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Jan 22, 2016 10:08 am
I'm thinking about writing a (small) tutorial on how to code a (little) game with devkitSMS/SMSlib.
Any suggestion about which game? :)
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14688
  • Location: London
Reply with quote
Post Posted: Fri Jan 22, 2016 4:55 pm
It depends what concepts, or API functions, you want to cover. I always planned to do Pong because you won't get too hung up in the game algorithm, and you can cover sprites, background changes (for the score) and managing a bit of game state without getting hung up with physics/level data, changing numbers of sprites, scrolling with tilemap updates, etc. I guess the sound part will be pretty much the same no matter what the game is.

Otherwise, some sort of simple generic platformer would be a good starting point for many games, but I think it would end up being a lot more code.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Jan 22, 2016 7:50 pm
Maxim wrote
It depends what concepts, or API functions, you want to cover.


Pretty much everything basic. So BG and sprites, loading assets, playing music and SFX. I'd love to turn the effort into a real game in the end, so I'm asking suggestions about a game I/we could port...
  View user's profile Send private message Visit poster's website
  • Joined: 01 Oct 2014
  • Posts: 27
  • Location: Sunnyvale, California
Reply with quote
Post Posted: Fri Jan 22, 2016 10:29 pm
You could do a devkitSMS version of the Snail Maze game.
Start by recreating the existing released version, then you could build on it with more tutorials, for example:
* adding SFX (the original has only music) that play when you "eat" bonuses/reach checkpoints in the maze,
* add a 2-player mode where 2 snails compete (Bomberman-style, or for example maybe some items/levers that close/open maze sections).
* add enemy AI (Pacman-style, or racing against AI?)
etc.
  View user's profile Send private message
  • Joined: 07 Oct 2015
  • Posts: 114
Reply with quote
Post Posted: Mon Jan 25, 2016 7:17 am
That's actually a pretty good idea. Everything else I think of would be too complicated for a tutorial.

If you go for platformers, maybe something simple in the lines of Teddy Boy would do the job. TB has scrolling, but it just draws a cyclic background and updates the scroll registers (no real scrolling of tiles in a tilemap is performed) - it manages good, back in the day I didn't realize that levels were just one screen big!

It would be interesting 'cause scrolling (even when you are not really scrolling) needs to adjust the sprite positions and the wrap-around thing adds some nice complexity to the picture.

Anyways, this would end up being more of a game programming tutorial than something purely focused on showcasing the library with a real example, so if it's the latter what you are aiming to, you'd rather do Snail Maze, Pong, or something that simple.
  View user's profile Send private message
  • Joined: 09 Jan 2012
  • Posts: 67
  • Location: Germany
Reply with quote
Post Posted: Sun Feb 07, 2016 10:45 pm
Hello again,
i wrote a simple brainfuck interpreter with devkitSMS and don't understand how i have to use vertical scrolling. Can somebody explain the issue?

#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "../devkitSMS/SMSlib/src/SMSlib.h"
#include "assets.h"

#define MEMSIZE (6 * 1024)
#define WHITE (0x3f)
#define BLACK (0x00)


const char bf_code[] = ">+++++++++[<+++++++++++>-]<[>[-]>[-]<<[>+>+<<-]>>[<<+>>-]>>>[-]<<<+++++++++<[>>>+<<[>+>[-]<<-]>[<+>-]>[<<++++++++++>>>+<-]<<-<-]+++++++++>[<->-]>>+>[<[-]<<+>>>-]>[-]+<<[>+>-<<-]<<<[>>+>+<<<-]>>>[<<<+>>>-]>[<+>-]<<-[>[-]<[-]]>>+<[>[-]<-]<++++++++[<++++++<++++++>>-]>>>[>+>+<<-]>>[<<+>>-]<[<<<<<.>>>>>-]<<<<<<.>>[-]>[-]++++[<++++++++>-]<.>++++[<++++++++>-]<++.>+++++[<+++++++++>-]<.><+++++..--------.-------.>>[>>+>+<<<-]>>>[<<<+>>>-]<[<<<<++++++++++++++.>>>>-]<<<<[-]>++++[<++++++++>-]<.>+++++++++[<+++++++++>-]<--.---------.>+++++++[<---------->-]<.>++++++[<+++++++++++>-]<.+++..+++++++++++++.>++++++++[<---------->-]<--.>+++++++++[<+++++++++>-]<--.-.>++++++++[<---------->-]<++.>++++++++[<++++++++++>-]<++++.------------.---.>+++++++[<---------->-]<+.>++++++++[<+++++++++++>-]<-.>++[<----------->-]<.+++++++++++..>+++++++++[<---------->-]<-----.---.>>>[>+>+<<-]>>[<<+>>-]<[<<<<<.>>>>>-]<<<<<<.>>>++++[<++++++>-]<--.>++++[<++++++++>-]<++.>+++++[<+++++++++>-]<.><+++++..--------.-------.>>[>>+>+<<<-]>>>[<<<+>>>-]<[<<<<++++++++++++++.>>>>-]<<<<[-]>++++[<++++++++>-]<.>+++++++++[<+++++++++>-]<--.---------.>+++++++[<---------->-]<.>++++++[<+++++++++++>-]<.+++..+++++++++++++.>++++++++++[<---------->-]<-.---.>+++++++[<++++++++++>-]<++++.+++++++++++++.++++++++++.------.>+++++++[<---------->-]<+.>++++++++[<++++++++++>-]<-.-.---------.>+++++++[<---------->-]<+.>+++++++[<++++++++++>-]<--.+++++++++++.++++++++.---------.>++++++++[<---------->-]<++.>+++++[<+++++++++++++>-]<.+++++++++++++.----------.>+++++++[<---------->-]<++.>++++++++[<++++++++++>-]<.>+++[<----->-]<.>+++[<++++++>-]<..>+++++++++[<--------->-]<--.>+++++++[<++++++++++>-]<+++.+++++++++++.>++++++++[<----------->-]<++++.>+++++[<+++++++++++++>-]<.>+++[<++++++>-]<-.---.++++++.-------.----------.>++++++++[<----------->-]<+.---.[-]<<<->[-]>[-]<<[>+>+<<-]>>[<<+>>-]>>>[-]<<<+++++++++<[>>>+<<[>+>[-]<<-]>[<+>-]>[<<++++++++++>>>+<-]<<-<-]+++++++++>[<->-]>>+>[<[-]<<+>>>-]>[-]+<<[>+>-<<-]<<<[>>+>+<<<-]>>>[<<<+>>>-]<>>[<+>-]<<-[>[-]<[-]]>>+<[>[-]<-]<++++++++[<++++++<++++++>>-]>>>[>+>+<<-]>>[<<+>>-]<[<<<<<.>>>>>-]<<<<<<.>>[-]>[-]++++[<++++++++>-]<.>++++[<++++++++>-]<++.>+++++[<+++++++++>-]<.><+++++..--------.-------.>>[>>+>+<<<-]>>>[<<<+>>>-]<[<<<<++++++++++++++.>>>>-]<<<<[-]>++++[<++++++++>-]<.>+++++++++[<+++++++++>-]<--.---------.>+++++++[<---------->-]<.>++++++[<+++++++++++>-]<.+++..+++++++++++++.>++++++++[<---------->-]<--.>+++++++++[<+++++++++>-]<--.-.>++++++++[<---------->-]<++.>++++++++[<++++++++++>-]<++++.------------.---.>+++++++[<---------->-]<+.>++++++++[<+++++++++++>-]<-.>++[<----------->-]<.+++++++++++..>+++++++++[<---------->-]<-----.---.+++.---.[-]<<<]";
char memory[MEMSIZE];
short memptr = 0;
int x=0, y=0;

void load_font (void) {
    unsigned char i, j;
    unsigned char buffer[32], *o, *d;

   o = font_fnt;
   for(i = 0; i != 96; i++) {
      d = buffer;
      for(j = 0; j != 8; j++) {
         *(d++) = *o;
         *(d++) = 0;
         *(d++) = 0;
         *(d++) = 0;
            o++;
      }
      SMS_loadTiles(buffer, i, 32);
   }
}


void putchar(char c) {
   if (c >= 32) {
        ++x;
      SMS_setTile(c - 32);
    } else if (c == 10 || c == 13) {
        x = 0;
        ++y;
    }
    if (x > 31) {x = 0; ++y;}
    if (y == 24) {y = 23; SMS_setBGScrollY(-8);}
    SMS_setNextTileatXY(x, y);
}

void interpret(int codeptr) {
    for(; codeptr < sizeof bf_code; ++codeptr) {
        //printf("%i. '%c' memptr=%d \n", codeptr, memory[memptr], memptr);
        switch(bf_code[codeptr]) {
        case '+': ++memory[memptr]; break;
        case '-': --memory[memptr]; break;
        case '<': --memptr; break;
        case '>': ++memptr; break;
        case '.': putchar(memory[memptr]);break;
        case ',': memory[memptr] = getchar();
        case '[': if (memory[memptr] != 0) {
                        interpret(codeptr + 1);
                        --codeptr;
                    } else {
                        char balance = 1;
                        do {
                            if (bf_code[++codeptr] == '[' ) ++balance;
                            else if (bf_code[codeptr] == ']' ) --balance;
                        } while(balance != 0);
                    };
                    break;
        case ']': return;
        default:;
        }
    }
}

int main() {
    int i;
    //load complete font to tile map
    load_font();
   //set two used colors
   SMS_setBGPaletteColor(0, BLACK);
    SMS_setBGPaletteColor(1, WHITE);
    //set cursor to top left
    SMS_setNextTileatXY(0, 0);
    SMS_displayOn();
   
    //interpreter init
    for(i=0; i < MEMSIZE; ++i)
        memory[i] = 0;
    interpret(0);
       
    while(1);
}

SMS_EMBED_SEGA_ROM_HEADER(9999, 0);
SMS_EMBED_SDSC_HEADER(0, 5, 2016, 1,  4, "DarkTrym\\2016", "Brainfuck","Brainfuck Demo");
[/code]
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Sat Feb 13, 2016 8:18 am
dark wrote
Hello again,
i wrote a simple brainfuck interpreter with devkitSMS and don't understand how i have to use vertical scrolling. Can somebody explain the issue?


I see few problems in your putchar. One is that you're not incrementing Y when X reaches 32, and you should reset X to 0 then.
If you want to use hardware scrolling, to use it correctly you should know that there are 28 lines in the screen tilemap, but only 24 lines are visible, so you should keep on incrementing Y up to 28, then wrap. Also you shouldn't decrement Y and scroll by -8 pixel, that's not how SMS hardware tilemap works. I mean, it's not moving your tiles one row up, it's displaying a part of the map on screen, according to scroll registers. So you should scroll at -8 to display the 25th line (Y=24) and scroll at -16 to display the 26th line (Y=25) and so on, I hope you get what I mean.
If I were you I wouldn't use hardware scroll, I would just wrap at 24th line and start over (at Y=0) as it's simpler and it gives exactly the same output.
  View user's profile Send private message Visit poster's website
  • Joined: 09 Jan 2012
  • Posts: 67
  • Location: Germany
Reply with quote
Post Posted: Sun Feb 14, 2016 10:38 am
Thank you for the insights.
And the wrong behaviour when a line ending occurs is fixed(missing #13 handling).

#include "../devkitSMS/SMSlib/src/SMSlib.h"
#include "assets.h"

/*
author: darktrym
license: new bsd
*/

#define SPACE (0)
#define MAXROW (24)
#define MAXCOL (32)
#define FONTLENGTH (96)
#define MEMSIZE (6 * 1024)
#define RED  (0x03)
#define BLUE (0x30)

const char bf_code[] = ">+++++++++[<+++++++++++>-]<[>[-]>[-]<<[>+>+<<-]>>[<<+>>-]>>>[-]<<<+++++++++<[>>>+<<[>+>[-]<<-]>[<+>-]>[<<++++++++++>>>+<-]<<-<-]+++++++++>[<->-]>>+>[<[-]<<+>>>-]>[-]+<<[>+>-<<-]<<<[>>+>+<<<-]>>>[<<<+>>>-]>[<+>-]<<-[>[-]<[-]]>>+<[>[-]<-]<++++++++[<++++++<++++++>>-]>>>[>+>+<<-]>>[<<+>>-]<[<<<<<.>>>>>-]<<<<<<.>>[-]>[-]++++[<++++++++>-]<.>++++[<++++++++>-]<++.>+++++[<+++++++++>-]<.><+++++..--------.-------.>>[>>+>+<<<-]>>>[<<<+>>>-]<[<<<<++++++++++++++.>>>>-]<<<<[-]>++++[<++++++++>-]<.>+++++++++[<+++++++++>-]<--.---------.>+++++++[<---------->-]<.>++++++[<+++++++++++>-]<.+++..+++++++++++++.>++++++++[<---------->-]<--.>+++++++++[<+++++++++>-]<--.-.>++++++++[<---------->-]<++.>++++++++[<++++++++++>-]<++++.------------.---.>+++++++[<---------->-]<+.>++++++++[<+++++++++++>-]<-.>++[<----------->-]<.+++++++++++..>+++++++++[<---------->-]<-----.---.>>>[>+>+<<-]>>[<<+>>-]<[<<<<<.>>>>>-]<<<<<<.>>>++++[<++++++>-]<--.>++++[<++++++++>-]<++.>+++++[<+++++++++>-]<.><+++++..--------.-------.>>[>>+>+<<<-]>>>[<<<+>>>-]<[<<<<++++++++++++++.>>>>-]<<<<[-]>++++[<++++++++>-]<.>+++++++++[<+++++++++>-]<--.---------.>+++++++[<---------->-]<.>++++++[<+++++++++++>-]<.+++..+++++++++++++.>++++++++++[<---------->-]<-.---.>+++++++[<++++++++++>-]<++++.+++++++++++++.++++++++++.------.>+++++++[<---------->-]<+.>++++++++[<++++++++++>-]<-.-.---------.>+++++++[<---------->-]<+.>+++++++[<++++++++++>-]<--.+++++++++++.++++++++.---------.>++++++++[<---------->-]<++.>+++++[<+++++++++++++>-]<.+++++++++++++.----------.>+++++++[<---------->-]<++.>++++++++[<++++++++++>-]<.>+++[<----->-]<.>+++[<++++++>-]<..>+++++++++[<--------->-]<--.>+++++++[<++++++++++>-]<+++.+++++++++++.>++++++++[<----------->-]<++++.>+++++[<+++++++++++++>-]<.>+++[<++++++>-]<-.---.++++++.-------.----------.>++++++++[<----------->-]<+.---.[-]<<<->[-]>[-]<<[>+>+<<-]>>[<<+>>-]>>>[-]<<<+++++++++<[>>>+<<[>+>[-]<<-]>[<+>-]>[<<++++++++++>>>+<-]<<-<-]+++++++++>[<->-]>>+>[<[-]<<+>>>-]>[-]+<<[>+>-<<-]<<<[>>+>+<<<-]>>>[<<<+>>>-]<>>[<+>-]<<-[>[-]<[-]]>>+<[>[-]<-]<++++++++[<++++++<++++++>>-]>>>[>+>+<<-]>>[<<+>>-]<[<<<<<.>>>>>-]<<<<<<.>>[-]>[-]++++[<++++++++>-]<.>++++[<++++++++>-]<++.>+++++[<+++++++++>-]<.><+++++..--------.-------.>>[>>+>+<<<-]>>>[<<<+>>>-]<[<<<<++++++++++++++.>>>>-]<<<<[-]>++++[<++++++++>-]<.>+++++++++[<+++++++++>-]<--.---------.>+++++++[<---------->-]<.>++++++[<+++++++++++>-]<.+++..+++++++++++++.>++++++++[<---------->-]<--.>+++++++++[<+++++++++>-]<--.-.>++++++++[<---------->-]<++.>++++++++[<++++++++++>-]<++++.------------.---.>+++++++[<---------->-]<+.>++++++++[<+++++++++++>-]<-.>++[<----------->-]<.+++++++++++..>+++++++++[<---------->-]<-----.---.+++.---.[-]<<<]";
char memory[MEMSIZE];
short memptr = 0;
int x=0, y=0;

void load_font (void) {
    unsigned char i, j;
    unsigned char buffer[32], *o, *d;

    o = font_fnt;
    for(i = 0; i != FONTLENGTH; i++) {
        d = buffer;
        for(j = 0; j != 8; j++) {
            *(d++) = *o;
            *(d++) = 0;
            *(d++) = 0;
            *(d++) = 0;
            o++;
        }
        SMS_loadTiles(buffer, i, 32);
    }
}

void delline() {
    char col=0;
    for (; col < MAXCOL; ++col) {
        SMS_setNextTileatXY(col, y);
        SMS_setTile(SPACE);
    }
}

void putchar(char ch) {
    //check boundary
    if (ch < 32 && ch != 10) return;
    if (x == MAXCOL || ch == 10 || ch == 13) {
        x = 0;
        ++y;
        delline();
    }
    if (y == MAXROW) {
        y = 0;
        delline();
    }
   
    if ((ch >= 32) && (ch < (FONTLENGTH + 32))) {
        SMS_setNextTileatXY(x++, y);
        SMS_setTile(ch - 32);
    }
}

void interpret(int codeptr) {
    for (; codeptr < sizeof bf_code; ++codeptr) {
        switch(bf_code[codeptr]) {
        case '+':    ++memory[memptr]; break;
        case '-':    --memory[memptr]; break;
        case '<':    --memptr; break;
        case '>':    ++memptr; break;
        case '.':    putchar(memory[memptr]);break;
        case ',':    memory[memptr] = SMS_getKeysStatus();
        case '[':    if (memory[memptr] != 0) {
                        interpret(codeptr + 1);
                        --codeptr;
                    } else {
                        char balance = 1;
                        do {
                            if (bf_code[++codeptr] == '[' ) ++balance;
                            else if (bf_code[codeptr] == ']' ) --balance;
                        } while(balance != 0);
                    }
                    break;
        case ']': return;
        }
    }
}

int main() {
    int i;
    //load complete font to tile map
    load_font();
    //set two used colors
    SMS_setBGPaletteColor(0, BLUE);
    SMS_setBGPaletteColor(1, RED);
    SMS_displayOn();
   
    //interpreter init
    for (i = 0; i < MEMSIZE; ++i)
        memory[i] = 0;
   
    interpret(0);
    //infinite loop   
    while (1);
}
SMS_EMBED_SEGA_ROM_HEADER(9999, 0);
SMS_EMBED_SDSC_HEADER(0, 5, 2016, 2,  14, "DarkTrym\\2016", "Brainfuck","Brainfuck Demo");
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Mon Feb 29, 2016 9:12 am
I'm happy it works, but I still see few errors (for instance you're returning if ch<32 and not =10 but later you check if it's =13 ... )
Also, you don't actually really have to use SMS_setNextTileatXY each time if you're not moving the 'cursor', as it's automatically moving to next char.
  View user's profile Send private message Visit poster's website
  • Joined: 09 Jan 2012
  • Posts: 67
  • Location: Germany
Reply with quote
Post Posted: Tue Mar 01, 2016 10:27 pm
Line endings in Brainfuck are not really specified. The most code i have found was for Windows Cmd so i went the easy way to detect the sequence and ignore the rest. The second point, yes i don't have to do this but i think here is explicit better than implicit.
  View user's profile Send private message Visit poster's website
  • Joined: 22 Mar 2015
  • Posts: 228
Reply with quote
Post Posted: Fri Mar 18, 2016 3:18 pm
Hi I would love to know how to download from github I'm looking for the .exe but I cant find it, besides it what is needed and what the size of all those would be approximately.
Thanks in advance.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Mar 18, 2016 3:48 pm
FeRcHuLeS wrote
I'm looking for the .exe but I cant find it


Sorry, I don't get what .exe you're actually searching. The devkit is a collection of tools and code that -together with SDCC compiler- will make possible for you to write/compile/execute C code on your favorite SEGA 8-bit console. What's the goal you're trying to achieve?
  View user's profile Send private message Visit poster's website
  • Joined: 22 Mar 2015
  • Posts: 228
Reply with quote
Post Posted: Fri Mar 18, 2016 6:31 pm
sverx wrote
FeRcHuLeS wrote
I'm looking for the .exe but I cant find it


Sorry, I don't get what .exe you're actually searching. The devkit is a collection of tools and code that -together with SDCC compiler- will make possible for you to write/compile/execute C code on your favorite SEGA 8-bit console. What's the goal you're trying to achieve?


I'm trying to get familiarized with thist development kit since I dont want to deal with z80 assembler that much so I dont know what to download and what to have installed in my computer to begin, I was asking about the size because at this moment I think I dont have the enough megas left in my internet plan.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Fri Mar 18, 2016 8:35 pm
FeRcHuLeS wrote
I was asking about the size because at this moment I think I dont have the enough megas left in my internet plan.


approx 4 MB for the SDCC snapshot (I'm assuming you're on Windows) and 143 KB for the whole of devkitSMS.
  View user's profile Send private message Visit poster's website
  • Joined: 29 Mar 2012
  • Posts: 879
  • Location: Spain
Reply with quote
Feature Request! :-P
Post Posted: Tue Mar 29, 2016 9:45 pm
I've just been peeking around shiru's NesLib, and it has a couple of very handy functions for fadings:


//fade current palette one brightness step to black

void __fastcall__ pal_fade(void);

//fade in current palette one brightess step to given one, both for bg and spr

void __fastcall__ pal_fade_to_all(const unsigned char *pal);

//fade in for bg only

void __fastcall__ pal_fade_to_bg(const unsigned char *pal);

//fade in for spr only

void __fastcall__ pal_fade_to_spr(const unsigned char *pal);


Are you planing to include something similar to devkitSMS?
  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14688
  • Location: London
Reply with quote
Post Posted: Wed Mar 30, 2016 7:39 am
It requires having the lib control the palette and store it in RAM to do the effects. You wouldn't be able to directly access the palette, instead you'd poke values into RAM and require an updatePalette() call in the VBlank to keep it up to date.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Mar 30, 2016 8:24 am
kusfo wrote
[...] Are you planing to include something similar to devkitSMS?


Mmm... I would say no. But it isn't hard to build your own function that reads a palette from ROM and generates a related palette into a small RAM buffer and then pushes data to CRAM using the appropriate function. Check this (source is here).
(also, I know at least two ways of fading colors, and there might be others too...)
  View user's profile Send private message Visit poster's website
  • Joined: 29 Mar 2012
  • Posts: 879
  • Location: Spain
Reply with quote
Post Posted: Wed Mar 30, 2016 9:03 am
I already developed one fade function for the Gaudream title screen, but i thouht that this is a common used feature, so maybe it woul be interesting to include it on devKitSMS

Anyway, i'll suggest any new useful feature that would come to my mind :-p
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Mar 30, 2016 9:29 am
kusfo wrote
Anyway, i'll suggest any new useful feature that would come to my mind :-p


Yes, please :) At the moment I've got a few ideas myself too, I will probably post a survey about it.

edit: also, if you feel like sharing code snippets, that would come handy :)
  View user's profile Send private message Visit poster's website
  • Joined: 28 May 2015
  • Posts: 118
Reply with quote
Post Posted: Wed Mar 30, 2016 10:08 pm
A random function would be great. I can not figure out how to do it in devkitSMS.
  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14688
  • Location: London
Reply with quote
Post Posted: Thu Mar 31, 2016 7:06 am
Try rand().
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Mar 31, 2016 8:22 am
Standard libraries provide already a way to generate random numbers, as Maxim pointed out. BTW they're quite slow so you either use them only when it's not time-critical (e.g. when preparing the level) or you can use them to populate an array with values you'll use later.
As for me, I'm always fine enough with a very simple approach: a 256 bytes array in ROM, filled with random values (taken from here for instance) and a simple macro:

#define FAKE_RAND()  (LUT_fake_rand[fake_rand_index++])


edit: of course fake_rand_index is an unsigned char
  View user's profile Send private message Visit poster's website
  • Joined: 12 Oct 2015
  • Posts: 183
  • Location: Ireland
Reply with quote
devkitSMS README.md
Post Posted: Thu Mar 31, 2016 12:46 pm
Hi - not sure if this has been mentioned but in the devkitSMS README.md I wasn't able to link my program with crt0_sms.rel and the library; i.e. was not producing your_program.ihx required for the final step.

I went thru Makefile from haroldoop and see that the README.md is missing "-o your_program.ihx" between "0xC000" and "crt0_sms.rel"

i.e. it now seems to work if I update to something like this (if you'd like to update the README)
sdcc -mz80 --no-std-crt0 --data-loc 0xC000 -o your_program.ihx crt0_sms.rel your_program.rel SMSlib.lib


Also, when compiling using sdcc -c -mz80 your_program.c there are 3x warning 158: "overflow in implicit constant conversion" from SMS_EMBED_SDSC_HEADER author,name,descr - doesn't matter what I enter here; is there any way to resolve these warnings?

Thanks!
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Thu Mar 31, 2016 1:02 pm
SteveProXNA wrote
[...] the README.md is missing "-o your_program.ihx"


Oops, true. I'm fixing it.

SteveProXNA wrote
Also, when compiling using sdcc -c -mz80 your_program.c there are 3x warning 158: "overflow in implicit constant conversion" from SMS_EMBED_SDSC_HEADER author,name,descr - doesn't matter what I enter here; is there any way to resolve these warnings?


It doesn't happen with my SDCC version, are you using a recent snapshot or and (old) official release? (BTW you can probably just ignore the warning...)
  View user's profile Send private message Visit poster's website
Reply to topic Goto page Previous  1, 2, 3, 4, 5, 6, 7 ... 14, 15, 16  Next



Back to the top of this page

Back to SMS Power!