Jump to content
senior_falcon

XB Game Developers Package

Recommended Posts

Posted (edited)

I have no doubt that a good programmer could write a program that goes directly from an XB program to a finished compiled program that is ready to run. Windows would have plenty of power to do this. On the TI99 it could probably be done if you used SAMS.

There are several reasons my compiler works the way it does.

1 - I'm not that good a programmer a good enough programmer to do that.

2 - most of this was developed on a real TI99 with the usual 32K memory limitations.

3 - because of the memory limitations I chose to save the XB program in merge format. This way each line can be read individually and processed.

4 - The compiler reads the XB merge file to make a DV80 file containing "threaded code." This way you can look at the code produced to see if it makes sense and if runtime routines can be made to process it. 

5 - now the runtime routines can be created to process the threaded code. Because the compiled code and runtime routines are text files they can be read easily which facilitates debugging.

6 - Since they are source code they must be compiled into object code.

7 - Now the compiled object code must be loaded. This is a little tricky because the code must be able to run in both XB and EA5. So there are custom loaders to handle the various possibilities.

 

Edited by senior_falcon
  • Like 5

Share this post


Link to post
Share on other sites
Posted (edited)
45 minutes ago, 1980gamer said:

I thought I was squared away...

Now I always get this error...   First it skips the Assembler Step.  So, I arrow up to run it.

image.thumb.png.c1f3978341524ce165be8076db53bcca.png

If it is skipping the assembler step, that's because you chose Y for "assembling with Asm994a."

You cannot mix and match. If you choose Y then you must use Asm994a. If you choose N then you must use the TI assembler.

Also, to use Asm994a, the runtime routines and the file being processed must be on the same disk.

Edited by senior_falcon
  • Like 5

Share this post


Link to post
Share on other sites
1 hour ago, senior_falcon said:

I have no doubt that a good programmer could write a program that goes directly from an XB program to a finished compiled program that is ready to run. Windows would have plenty of power to do this. On the TI99 it could probably be done if you used SAMS.

There are several reasons my compiler works the way it does.

1 - I'm not that good a programmer

2 - most of this was developed on a real TI99 with the usual 32K memory limitations.

3 - because of the memory limitations I chose to save the XB program in merge format. This way each line can be read individually and processed.

4 - The compiler reads the XB merge file to make a DV80 file containing "threaded code." This way you can look at the code produced to see if it makes sense and if runtime routines can be made to process it. 

5 - now the runtime routines can be created to process the threaded code. Because the compiled code and runtime routines are text files they can be read easily which facilitates debugging.

6 - Since they are source code they must be compiled into object code.

7 - Now the compiled object code must be loaded. This is a little tricky because the code must be able to run in both XB and EA5. So there are custom loaders to handle the various possibilities.

 

Thx, makes sense now ;)

  • Like 1

Share this post


Link to post
Share on other sites
2 hours ago, senior_falcon said:

If it is skipping the assembler step, that's because you chose Y for "assembling with Asm994a."

You cannot mix and match. If you choose Y then you must use Asm994a. If you choose N then you must use the TI assembler.

Also, to use Asm994a, the runtime routines and the file being processed must be on the same disk.

This solved it!

I had tried every combo to try to make the process work.  Then I didn't remember what worked the other night at 3:30am   Go figure!

 

Anyway,  Now I cannot duplicate my original problem!

 

Maybe if I go back to 7.. But I don't want to break it.  LOL 

 

Thanks for the help.  I had been going crazy and giving up.   I downloaded the latest and couldn't sleep, so I started trying things again.  Bingo.

  • Like 2

Share this post


Link to post
Share on other sites
Posted (edited)

Here is a short video showing how to compile a simple missing link program. I chose the regular version of TML because it is a little slower than the XB 2.8 G.E.M. version, so the increase in speed is greater.

I added: 1 CALL LINK("TML16")

Then loaded and ran UC2LC, a utility that converts all the CALL LINKs to lower case. (This is necessary to tell the compiler that they are user CALL LINKs that are not built into the compiler like the XB256 routines.) Saved, compiled and assembled as usual by pressing Enter repeatedly. At the loader I chose "Y" for "Using Assembly Support?," then pressed Enter to use the suggested file name. Then used DSK1.TMLC for the assembly support routines. Pressed enter at the prompts until it was done. I first ran the XB version, then used the Editor Assembler cartridge to load and run DSK2.POLYL-E just to show that you can save as EA5.

POLY.gif

 


10 CALL LINK("CLEAR"):: ANG=RND*90+90 :: CALL LINK("PUTPEN",92,120,0)
20 FOR I=1 TO 130 :: CALL LINK("FWD",I,ANG):: NEXT I
30 GOTO 10

 

Edited by senior_falcon
  • Like 4

Share this post


Link to post
Share on other sites
7 hours ago, senior_falcon said:

1 - I'm not that good a programmer

"Not that good", only freaking amazing. ;)

 

  • Like 5

Share this post


Link to post
Share on other sites
7 hours ago, senior_falcon said:

1 - I'm not that good a programmer

 

Your tongue is coming through your cheek! 😊

 

...lee

  • Like 3

Share this post


Link to post
Share on other sites

There seems to be a trend these days to save compiled programs as EA5 and convert them into cartridges. This makes a nice, neat package and works fine for self contained programs.

On the other hand, saving a compiled program as XB has other advantages. Remember that, as far as XB is concerned, the compiled program is just a large assembly language subroutine. So it is possible to write an autoloading menu program in XB, and use it to select different compiled programs on the disk. When the compiled program ends it returns back to XB. If the next statement in the XB program is RUN "DSK1.MENU" then the menu program will be loaded and you can choose a different compiled program. Below is a short tutorial showing how to do this. This demo runs 2 programs using compiled TML. One program draws a polyspiral; the other draws circles using CALL LINK("PR") to make an interesting graphics demo. This is all done on DSK1.

First the menu program:

10 CALL CLEAR :: PRINT "PRESS:":"1 - POLYSPIRAL":"2 - CIRCLES WITH PENREVERSE"
20 CALL KEY(0,K,S):: IF K=49 THEN CALL CLEAR :: RUN "DSK1.POLY-X"
21 IF K=50 THEN CALL CLEAR :: RUN "DSK1.CIRCLES4-X"
22 GOTO 20

Save this - SAVE DSK1.TMLMENU

 

Here are the two programs. The first is POLY and the second is CIRCLES4

10 !CALL LINK("tml16")
20 FOR I=1 TO 500 :: CALL LINK("FWD",I,123):: NEXT I
30 CALL LINK("GRAFIX")


80 !CALL LINK("tml16")
100 CALL LINK("PR"):: FOR K=1 TO 2 :: FOR C=1 TO 90 STEP 1 :: FOR I=-1 TO 1 STEP 2 :: FOR J=-1 TO 1 STEP 2 :: CALL LINK("CIRCLE",96+I*C,120+J*C,C,0):: NEXT J :: NEXT I :: NEXT C :: NEXT K :: CALL LINK("PD")
120 CALL LINK("GRAFIX")

Note the CALL LINK("GRAFIX") at the end of the programs This sets the graphics mode to the normal XB mode so the XB menu program will look right.
 

These get compiled as described the XB COMPILER docs in the Juwel package, pages 17-19. Be sure to uncomment the first lines. Then they are saved as XB programs (POLY-X and CIRCLES4-X), and of course tested to be sure they work.

 

Now let's put it all together

Start by modifying TMLC, then save it: SAVE "DSK1.LOADTMLC"

10 CALL LOAD(8192,255,172):: CALL LINK("X")

10 CALL INIT::CALL LOAD(8192,255,172):: CALL LINK("X")::RUN "DSK1.TMLMENU"

This does CALL INIT, loads the compiled version of The Missing Link into low memory, then runs the TMLMENU program. This can be saved as LOAD if you want it to autoload. Once this has run, the Missing Link routines are in low memory and do not have to be loaded again as long as you do not CALL INIT or otherwise do something to overwrite them.

 

Now let's fix the two compiled programs. The XB part looks the same for both. We just need to add RUN "DSK1.TMLMENU" to run the menu program.

10 CALL LOAD(8192,255,158):: CALL LINK("RUN")

1 !POLYSPIRAL or CIRCLES4PR
10 CALL LOAD(8192,255,158):: CALL LINK("RUN"):: RUN "DSK1.TMLMENU"

Save them as POLY-X and CIRCLES4-X

 

Now to test:

RUN "DSK1.LOADTMLC". This loads The Missing Link routines and runs the menu program. In the menu program, press 1 for POLYSPIRAL or 2 for CIRCLES WITH PEN REVERSE

The program will run and when finished it will reload the menu program.

TMLCMENU.zip

TMLCMENU.gif

 

 

 

 

 

 

 

  • Like 8
  • Thanks 2

Share this post


Link to post
Share on other sites

I'm having trouble running these program on JS99er.net, but they work on Classic99. The circles only work when I disable the F18A emulation, and the polyspiral doesn't run at all. Can you confirm that it's working on real hardware?

Share this post


Link to post
Share on other sites
51 minutes ago, Asmusr said:

I'm having trouble running these program on JS99er.net, but they work on Classic99. The circles only work when I disable the F18A emulation, and the polyspiral doesn't run at all. Can you confirm that it's working on real hardware?

It turns out polyspiral wasn't working because I had 'debug reset' enabled where it's filling the RAM with random values. 

  • Like 2

Share this post


Link to post
Share on other sites

Sounds like polyspiral works for you now. What about the circles program?

I can send these over to the real TI tonight to see if they work. They should - TMLDEMO worked fine on real hardware.

Share this post


Link to post
Share on other sites
1 hour ago, senior_falcon said:

Sounds like polyspiral works for you now. What about the circles program?

Yes. The circles program still only works when I disable the F18A emulation, but it looks like a problem with the emulation.

Share this post


Link to post
Share on other sites
15 minutes ago, Asmusr said:

Yes. The circles program still only works when I disable the F18A emulation, but it looks like a problem with the emulation.

Fixed

  • Like 4

Share this post


Link to post
Share on other sites

I just sent this over to the real TI99 and it works fine using an HRD 4000.

TMLC has 2 workspaces in scratchpad ram, which helped a lot. But it is noticeably snappier when running on the 16 bit data bus vs. the 8 bit data bus.

To do the circles program takes 54 seconds vs. 74 seconds.

  • Like 3

Share this post


Link to post
Share on other sites
Posted (edited)

Having used it way more than anyone else on the planet, it is probably not surprising that tmop69 would discover a bug in the compiler. This is a tricky one that almost never manifests itself.

This only happens when you are using IF THEN ELSE and have a GOSUB in the program line. If low byte of the line number is 134 then the code generated by the compiler is faulty. So any line number with a combination of N*256+134 such as 134, 390, 646, etc. could cause trouble.

I believe this has been fixed in a way that will not cause other unexpected bugs to appear.

I will post the revised version once tmop69 has verified that it is actually fixed.

 

Edited by senior_falcon
  • Like 9

Share this post


Link to post
Share on other sites
Posted (edited)

Here is XBGDP20210628. the release version of the XB Game Developer's Package "Juwel"

The zipped folder contains two folders. One is JUWEL which contains the latest versions of the compiler and XB256. Compiled programs can now use assembly support routines.

The other folder is XB28GEM,  which contains XB 2.8 G.E.M. and its documentation. This offers many improvements over XB 2.7.  and really unlocks the graphics potential of the TI-99/4A. XB can now use bit mapped graphics, 40 column text mode, 80 column text mode and XB256, all available from the main menu.

 

(edit 2 July) This new package contains a revised version of XB 2.8 G.E.M. that corrects a minor bug when loading EA5 programs.  Otherwise it is identical to the previous version.

 

XBGDP20210702.zip

Edited by senior_falcon
  • Like 8

Share this post


Link to post
Share on other sites

In one of the other threads the question came up about whether Tursi's "Flicker" routine could be used with a compiled program. Yes, it can be, and here is how to do it.

It worked when I compiled it, but for the life of me I could not understand how it was able to find its way back to the compiled XB program. Here is the code with a couple of hilighted modifications that I know will work.

Spoiler

* Flicker for XB Sprites
* This is NOT a final version, this will break disk access
* (May work with Classic99's disk driver, but don't rely on it)
*
* -- PROOF OF CONCEPT ONLY --

    DEF FLICK

* XB equates
VMBR equ >202c
VMBW equ >2024

* sprite information
SNEW equ >3F00            * output vdp address
NEWMAX equ >3F70        * where it ends - must be same size as SMAX-SBASE
SBASE equ >0300            * where XB writes sprite data
SMAX equ >0370            * address where XB sprite data ends (28 sprites here)
GPLR11 equ >83F6        * GPLWS r11 for return
data    DATA >0000        * where we are in the rotation
wp      bss 32            * use a private workspace

*YOU CAN STAY IN THE GPL WORKSPACE HERE.   
FLICK    li r0,>0300            * current sprite address
    mov r0,@data
    
    li r0,INT1            * load address of interrupt routine
    mov r0,@>83c4        * set it in the hook

*TURSI'S PROGRAM "FELL THROUGH" TO THE INTERRUPT ROUTINE AND THE RETURN ADDRESS TO COMPILED CODE MAY GET LOST

    B @>0070        RETURN TO xb            B @>0070 WILL DEFINITELY GET YOU BACK TO THE COMPILED CODE  

*************************************************************************
*INTERRUPT ROUTINE IS BELOW
    
INT1    LWPI WP            load our workspace

    li r12,SNEW            * output address
    mov @data,r0        * current position in the rotation
ILP
    li r1,wp+6            * use private R3-R10 (16 bytes = 4 sprites)
    li r2,16
    blwp @VMBR            * read XB sprite table
    
    ai r0,16            * write it rotated by 4 sprites
    ci r0,SMAX            * are we at the end?
    jne ISK
    li r0,SBASE            * back to beginning of table
ISK
    mov r0,@data        * save it
    mov r12,r0            * output address
    blwp @VMBW            * and write it
    mov @data,r0        * get back original table, at next offset
    
    ai r12,16            * next output
    ci r12,NEWMAX        * are we done?
    jne ILP                * loop if needed
    
    li r0,>d000            * write end of sprite table tag
    movb r0,@>8c00        * relies on VDP address still being valid!

    li r0,>fe85            * going to move SAL to >3F00 (disk buffers)
    movb r0,@>8c02
    swpb r0
    movb r0,@>8c02
    
    mov @data,r0        * once more to update the base
    ai r0,16            * 4 sprites later
    ci r0,SMAX            * check for end
    jne ISK2
    li r0,SBASE            * wrap around if end
ISK2
    mov r0,@data        * save it for next time


DONE    LWPI >83E0
    b *r11                done with interrupt, go back
    
    END
   

In the folder are the files XB_FLICKER.OBJ (with the changes shown above) and FLICKER. Let's put that in disk 2

First test it out in XB.

CALL INIT

CALL LOAD("DSK2.XB_FLICKER.OBJ")     loads the assembly code

CALL LINK("FLICK")                               starts the interrupt routine

OLD DSK2.FLICKER                               loads the XB test program

RUN

 

Now that you have seen it work, let's compile it.

First we modify XB_FLICKER.OBJ to be compatible with the compiler. With the "Juwel" disk in drive #1:

CALL INIT

CALL LOAD("DSK2.XB_FLICKER.OBJ")     loads the assembly code to low memory

CALL LOAD("DSK1.FIXAL")                     load a utility to modify the assembly code to be compatible with compiled code. (on the Juwel disk)

CALL LINK("X")                                     runs the utility and makes an XB loader with embedded assembly code

SAVE DSK2.XBFLICKERC                        save the modified program

 

Now we compile the test program FLICKER

With the Juwel disk in drive 1, quit, select XB at the menu (option 2), and at the Juwel menu select XB

OLD DSK2.FLICKER

Now we add one line to the program which will start up the interrupt routine:

4 CALL LINK("FLICK")

Now we load a utility that converts all the CALL LINK names to lower case

CALL LOAD("DSK1.UC2LC")      utility is on the Juwel disk

CALL LINK("X")                       run the utility

(since there is just one CALL LINK, you could have made line 4 CALL LINK("flick"))

List the program to verify that FLICK is lower case, then SAVE. Autocomplete takes over and the compilation process proceeds normally.

 

When you come to the loader you are asked "Using Assembly Support?".  Press Y

You are prompted for the filename, which should be DSK1.FLICKER.OBJ   Press enter

You are prompted for the assembly routines to load. Enter DSK2.XBFLICKERC, which was the first file we created.

Then the usual routine of pressing Enter at the prompts. As usual you can save as EA5 (FLICKER-E) and XB. (FLICKER-X) 

Type RUN to test it out.

 

One more step is needed if you want to load the program from XB. The XB program must be in two parts.
You just saved the compiled XB part of the program, FLICKER-X. Now let's create the first part.
OLD DSK2.XBFLICKERC
Modify line 10:
10 CALL INIT :: CALL LOAD(8192,255,172):: CALL LINK("X"):: RUN "DSK2.FLICKER-X" Then:
SAVE DSK2.FLICKER-C
FLICKER-C must run first. When it runs it the assembly routines are loaded to low memory, then it loads and runs FLICKER-X.

 

FLICKER.zip

 

FLICKER.gif

 

 

 

 

 

 

  • Like 5

Share this post


Link to post
Share on other sites
5 minutes ago, HOME AUTOMATION said:

Umm, just an observation8)...

...In the compiled version, one of the sprites, remains in a fixed position.

If it's the same as my demo program, the velocity is random and 0 was possible, so that's probably not a bug :)

 

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
5 hours ago, senior_falcon said:

In one of the other threads the question came up about whether Tursi's "Flicker" routine could be used with a compiled program. Yes, it can be, and here is how to do it.

It worked when I compiled it, but for the life of me I could not understand how it was able to find its way back to the compiled XB program. Here is the code with a couple of hilighted modifications that I know will work.

  Reveal hidden contents

* Flicker for XB Sprites
* This is NOT a final version, this will break disk access
* (May work with Classic99's disk driver, but don't rely on it)
*
* -- PROOF OF CONCEPT ONLY --

    DEF FLICK

* XB equates
VMBR equ >202c
VMBW equ >2024

* sprite information
SNEW equ >3F00            * output vdp address
NEWMAX equ >3F70        * where it ends - must be same size as SMAX-SBASE
SBASE equ >0300            * where XB writes sprite data
SMAX equ >0370            * address where XB sprite data ends (28 sprites here)
GPLR11 equ >83F6        * GPLWS r11 for return
data    DATA >0000        * where we are in the rotation
wp      bss 32            * use a private workspace

*YOU CAN STAY IN THE GPL WORKSPACE HERE.   
FLICK    li r0,>0300            * current sprite address
    mov r0,@data
    
    li r0,INT1            * load address of interrupt routine
    mov r0,@>83c4        * set it in the hook

*TURSI'S PROGRAM "FELL THROUGH" TO THE INTERRUPT ROUTINE AND THE RETURN ADDRESS TO COMPILED CODE MAY GET LOST

    B @>0070        RETURN TO xb            B @>0070 WILL DEFINITELY GET YOU BACK TO THE COMPILED CODE  

*************************************************************************
*INTERRUPT ROUTINE IS BELOW
    
INT1    LWPI WP            load our workspace

    li r12,SNEW            * output address
    mov @data,r0        * current position in the rotation
ILP
    li r1,wp+6            * use private R3-R10 (16 bytes = 4 sprites)
    li r2,16
    blwp @VMBR            * read XB sprite table
    
    ai r0,16            * write it rotated by 4 sprites
    ci r0,SMAX            * are we at the end?
    jne ISK
    li r0,SBASE            * back to beginning of table
ISK
    mov r0,@data        * save it
    mov r12,r0            * output address
    blwp @VMBW            * and write it
    mov @data,r0        * get back original table, at next offset
    
    ai r12,16            * next output
    ci r12,NEWMAX        * are we done?
    jne ILP                * loop if needed
    
    li r0,>d000            * write end of sprite table tag
    movb r0,@>8c00        * relies on VDP address still being valid!

    li r0,>fe85            * going to move SAL to >3F00 (disk buffers)
    movb r0,@>8c02
    swpb r0
    movb r0,@>8c02
    
    mov @data,r0        * once more to update the base
    ai r0,16            * 4 sprites later
    ci r0,SMAX            * check for end
    jne ISK2
    li r0,SBASE            * wrap around if end
ISK2
    mov r0,@data        * save it for next time


DONE    LWPI >83E0
    b *r11                done with interrupt, go back
    
    END
   

In the folder are the files XB_FLICKER.OBJ (with the changes shown above) and FLICKER. Let's put that in disk 2

First test it out in XB.

CALL INIT

CALL LOAD("DSK2.XB_FLICKER.OBJ")     loads the assembly code

CALL LINK("FLICK")                               starts the interrupt routine

OLD DSK2.FLICKER                               loads the XB test program

RUN

 

Now that you have seen it work, let's compile it.

First we modify XB_FLICKER.OBJ to be compatible with the compiler. With the "Juwel" disk in drive #1:

CALL INIT

CALL LOAD("DSK2.XB_FLICKER.OBJ")     loads the assembly code to low memory

CALL LOAD("DSK1.FIXAL")                     load a utility to modify the assembly code to be compatible with compiled code. (on the Juwel disk)

CALL LINK("X")                                     runs the utility and makes an XB loader with embedded assembly code

SAVE DSK2.XBFLICKERC                        save the modified program

 

Now we compile the test program FLICKER

With the Juwel disk in drive 1, quit, select XB at the menu (option 2), and at the Juwel menu select XB

OLD DSK2.FLICKER

Now we add one line to the program which will start up the interrupt routine:

4 CALL LINK("FLICK")

Now we load a utility that converts all the CALL LINK names to lower case

CALL LOAD("DSK1.UC2LC")      utility is on the Juwel disk

CALL LINK("X")                       run the utility

(since there is just one CALL LINK, you could have made line 4 CALL LINK("flick"))

List the program to verify that FLICK is lower case, then SAVE. Autocomplete takes over and the compilation process proceeds normally.

 

When you come to the loader you are asked "Using Assembly Support?".  Press Y

You are prompted for the filename, which should be DSK1.FLICKER.OBJ   Press enter

You are prompted for the assembly routines to load. Enter DSK2.XBFLICKERC, which was the first file we created.

Then the usual routine of pressing Enter at the prompts. As usual you can save as EA5 (FLICKER-E) and XB. (FLICKER-X) 

Type RUN to test it out.

 

One more step is needed if you want to load the program from XB. The XB program must be in two parts.
You just saved the compiled XB part of the program, FLICKER-X. Now let's create the first part.
OLD DSK2.XBFLICKERC
Modify line 10:
10 CALL INIT :: CALL LOAD(8192,255,172):: CALL LINK("X"):: RUN "DSK2.FLICKER-X" Then:
SAVE DSK2.FLICKER-C
FLICKER-C must run first. When it runs it the assembly routines are loaded to low memory, then it loads and runs FLICKER-X.

 

FLICKER.zip 28.64 kB · 2 downloads

 

FLICKER.gif

 

 

 

 

 

 

 

First compiled game using the routine from @Tursi using the integration instructions from @senior_falconArchon! 🙂 

 

  • Like 1

Share this post


Link to post
Share on other sites

XB Flicker quick integration instructions for the lazy programmer:

 

- copy XBFLICKERC from the zip in your JUWEL directory;
- load your XB program to compile, add at the beginning of your code the line:
   1 CALL LINK("flick")
- save and compile it;
- press Y at the Loader request "Using Assembly Support?" and enter DSK1.XBFLICKERC at the request of the assembly routines to load.

 

That's all! 🙂 For a detailed explanation of the steps check the original post above. ;-) 

 

The procedure is compatible with the XB256.
 

  • Like 2

Share this post


Link to post
Share on other sites

That's a nice, concise distillation of the steps needed. Be sure CALL LINK("flick") uses lower case.

Tursi is right; the program uses random velocities. Sometimes the XB version has one or two stationary sprites and sometimes the compiled version. When the compiled version starts up, all 28 sprites are lined up so there is massive flicker at first until they move to different lines.

@Tursi, if you want to develop a more polished version, you should be able to use the edit/recall buffer v08C0 to v0957 for your buffer instead of using the disk buffer.

  • Like 2

Share this post


Link to post
Share on other sites

Another improvement could be the ability to turn the routine ON or OFF. If I peek at -31877 and there are 5 sprites on a row I can activate the routine, deactivate if not. This will reduce the general flickering of sprites added by this code (at least on LCD, not checked the effect on CRT on real iron). The ideal would be an automatic, interrupt driven, code, but not sure if it's feasible/integrable with XB and the compiler.

  • Like 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...