Jump to content
IGNORED

CART.MAC Helper: Overlapping RAM Segments


DZ-Jay

Recommended Posts

Hello:

 

I got inspired this weekend and implemented a helper macro library to use with CART.MAC that supports overlapping RAM segments. It's rather simple at the moment. I toyed with the idea of making it more comprehensive--allowing for extending and appending segments or multiple super-segments each containing overlapping sub-segments--but my priority right now is finishing Christmas Carol.

 

I'll certainly expand it in the future. It will form part of the upcoming P-Machinery v2.0.

 

Anyway, the library offers the following macros:

 

;; ========================================================================
;; RAMSTART Starts RAM segment allocation.
;; RAMEND Ends RAM segment allocation.
;; RAMSEG_START Defines a new RAM segment and opens it for use.
;; RAMSEG_END Closes an open RAM segment.
;; ========================================================================

 

It works similar to the "ROMSEG" macro in CART.MAC: You call RAMSTART somewhere at the top of your code, to initialize RAM segmentation and mark the beginning of available RAM; and then RAMEND at the end to close any outstanding open segments. In between, you may define as many segments as you like, and each one resets the RAM allocation pointers in CART.MAC to the same base address.

 

RAMSTART also defines a default segment called "Global" that is never overlapped. This allows you to define global variables that will be shared across all your game states, without the threat of accidental overwrite by your other segments. If you don't need this global segment, just ignore it and call RAMSEG_START() to define your own segments.

 

Along with the "RAMSEG.MAC" library, I include a modified "STATS.MAC" library from P-Machinery v1.0 to output useful information regarding your RAM and ROM segment usage. You include this library at the very end of your code--after calling ROMEND.

 

Below is an example usage, taken from Christmas Carol. The game is split into mutually exclusive states, and each one requiring its own set of variables. There are also variables that are shared across all states, so they are perfect candidates for the "Global" segment.

 

Let me know if you have any questions.

 

-dZ.

 


;;==========================================================================;;
;; Title:       Christmas Carol vs. The Ghost Of Christmas Presents         ;;
;; By:          DZ-Jay                                                      ;;
;; Description: An original concept game, built on the P-MACH engine,       ;;
;;              originally developed to port Pac-Man to the Intellivision.  ;;
;;                                                                          ;;
;; Copyright 2010-2012, James Pujals (DZ-Jay), <dz-game@techunlimited.net>. ;;
;;==========================================================================;;

;; =====================================================
;; ROM HEADER SET-UP
;; =====================================================
       INCLUDE "cart.mac"
       INCLUDE "macro/ramseg.mac"

       ROMSETUP 42K, 2012, "Christmas Carol", BOOT_UP, STACK_SIZE

       ; ...

; ======================================================
; GAME WORLD STATE
; ======================================================
       RAMSTART                        ; Start "Global" segment

PLAYER_INFO      STRUCT  0
@@Rank           SCRATCH 1
@@ScoreRevs      SCRATCH 1
@@Score          SCRATCH 2
@@Lives          SCRATCH 1
                ENDS

; ======================================================
; PRACTICE MODE STATE
; ======================================================
       RAMSEG_START(Practice)          ; Start "Practice" segment

SCROLL_INFO      STRUCT  0
@@Delay          SCRATCH 1
@@State          SCRATCH 1
@@Flags          SCRATCH 1
@@Column         SCRATCH 1
@@PadCol         SCRATCH 1
@@RateFrac       SCRATCH 1
                ENDS

       ; ...

; ======================================================
; LEVEL PLAY STATE
; ======================================================
       RAMSEG_START(Level)             ; Start "Level" segment

LEVEL_INFO       STRUCT  0
@@CandyCnt       SCRATCH 1
@@SnoflkCnt      SCRATCH 1
@@Deaths         SCRATCH 1
@@Perfect        SCRATCH 1
@@PresentCnt     SCRATCH 1
@@PresentVec     SCRATCH 1
@@BonusMult      SCRATCH 1
@@PlayerHits     SCRATCH 1
                ENDS

       ; ...

;; =====================================================
;;  END OF LINE.
;; =====================================================
       RAMEND                          ; End RAM segmentation

       ROMEND
       INCLUDE "macro/stats.mac"

 

RAMSEG Lib.zip

Edited by DZ-Jay
Link to comment
Share on other sites

Did they change something in the forum software? The source code is being mangled now!

 

It used to be that if I submit a post built using raw BB code, it would go through perfectly fine, unless I clicked on "Preview," which caused it to convert spaces into tabs.

 

Now, I can't get it to keep the original spaces I put in. It's like it's turning 4 spaces into a tab, and then expanding each tab into 8 spaces.

 

Stupid software.

 

-dZ.

Edited by DZ-Jay
Link to comment
Share on other sites

Did they change something in the forum software? The source code is being mangled now!

 

It used to be that if I submit a post built using raw BB code, it would go through perfectly fine, unless I clicked on "Preview," which caused it to convert spaces into tabs.

 

Now, I can't get it to keep the original spaces I put in. It's like it's turning 4 spaces into a tab, and then expanding each tab into 8 spaces.

 

Stupid software.

 

-dZ.

 

I find if the text I cut/paste has tabs in it, it'll show up broken then. I have to make sure whatever I'm copying from hands everything over as spaces.

 

And yes, I find it annoying that I have to use the raw BBcode editor to begin with to get code posted properly, and even then it's a careful dance to make sure it remains unadulterated -- you can't flip the "raw"/"cooked" switch at all.

 

Really very annoying. (While I'm complaining, why are source code filetypes disallowed for upload, but zip files containing the same source code allowed? Common source code types should be treated like text...)

Link to comment
Share on other sites

This is a quick test to see if the raw BBcode editor is b0rken or not.

 

0 spaces
1 space
 2 spaces
  3 spaces
   4 spaces
    5 spaces
     6 spaces
      7 spaces
       8 spaces
        9 spaces
         10 spaces
          11 spaces
           12 spaces
            13 spaces
             14 spaces
              15 spaces
               16 spaces

0 spaces
1 space
 2 spaces
  3 spaces
   4 spaces
    5 spaces
     6 spaces
      7 spaces
       8 spaces
        9 spaces
         10 spaces
          11 spaces
           12 spaces
            13 spaces
             14 spaces
              15 spaces
               16 spaces

Link to comment
Share on other sites

I find if the text I cut/paste has tabs in it, it'll show up broken then. I have to make sure whatever I'm copying from hands everything over as spaces.

 

And yes, I find it annoying that I have to use the raw BBcode editor to begin with to get code posted properly, and even then it's a careful dance to make sure it remains unadulterated -- you can't flip the "raw"/"cooked" switch at all.

 

Well, that's how it used to be. My complaint right now is that it didn't work with the BBcode editor, nor the regular editor. My source code never has tabs in it. I even opened it with a HEX editor to make sure.

 

This is a very recent change.

 

-dZ.

Link to comment
Share on other sites

This is a quick test to see if the raw BBcode editor is b0rken or not.

 

0 spaces
1 space
2 spaces
3 spaces
4 spaces
5 spaces
6 spaces
7 spaces
8 spaces
9 spaces
10 spaces
11 spaces
12 spaces
13 spaces
14 spaces
15 spaces
16 spaces

0 spaces
1 space
2 spaces
3 spaces
4 spaces
5 spaces
6 spaces
7 spaces
8 spaces
9 spaces
10 spaces
11 spaces
12 spaces
13 spaces
14 spaces
15 spaces
16 spaces

 

Seems broken on multiples of four.

 

-dZ.

Link to comment
Share on other sites

This is a quick test to see if the raw BBcode editor is b0rken or not.

 

0 spaces
1 space
2 spaces
3 spaces
4 spaces
5 spaces
6 spaces
7 spaces
8 spaces
9 spaces
10 spaces
11 spaces
12 spaces
13 spaces
14 spaces
15 spaces
16 spaces

0 spaces
1 space
2 spaces
3 spaces
4 spaces
5 spaces
6 spaces
7 spaces
8 spaces
9 spaces
10 spaces
11 spaces
12 spaces
13 spaces
14 spaces
15 spaces
16 spaces

 

Seems broken on multiples of four.

 

-dZ.

 

Doubly broken on reply: spaces were coalesced.

Edited by DZ-Jay
Link to comment
Share on other sites

Hello:

 

I got inspired this weekend and implemented a helper macro library to use with CART.MAC that supports overlapping RAM segments. It's rather simple at the moment. I toyed with the idea of making it more comprehensive--allowing for extending and appending segments or multiple super-segments each containing overlapping sub-segments--but my priority right now is finishing Christmas Carol.

 

I'll certainly expand it in the future. It will form part of the upcoming P-Machinery v2.0.

 

Now actually commenting on the meat... Cool stuff! Basically, if you have pieces of code that you know are mutually exclusive, you can overlay their RAM areas, to save on overall RAM usage. Still, it's up to you to ensure that two different variables in two different RAM segments are never actually needed at the same time.

 

How do you declare variables that are needed "everywhere"? I assume you must declare these before "RAMSTART"? And what about variables that are needed in two places, but not all? Do those have to fall back to "everywhere"?

 

(I admit I haven't peeked at the source yet.)

 

BTW, one other observation: If you're using the 42K map, you're not guaranteed that two consecutive calls to SYSTEM will return contiguous words. (Likewise for SCRATCH if you have REQ_ECS=1.) The only way to ensure contiguous memory allocation is to ask for multiple bytes/words in a single call to SCRATCH or SYSTEM. For example:

 

 

 

.player_info      SCRATCH  5

PLAYER_INFO       STRUCT  .player_info

@@Rank            EQU     $ + 0

@@ScoreRevs       EQU     $ + 1

@@Score           EQU     $ + 2

@@Lives           EQU     $ + 4

                  ENDS

 

 

 

To make this cleaner probably requires some support inside AS1600. But, it seems like some creative macros for defining structures might work OK even without special support. Thoughts?

 

(PS. I found a really crappy way of hacking around the formatting issues... I dropped using the code tag, and instead, copy/pasted my text into GEdit, did a search and replace, replacing space characters with an "emspace" I copied out of an HTML 4.0 reference. I pasted the result back here and set the font to Courier. Ugly hack, but it worked.)

Edited by intvnut
Link to comment
Share on other sites

Yeah, I first noticed these formatting shenanigans in the fixed-point multiply thread, both the leading-space deletion and the spurious tabs.

 

AA forums: Stop making our assembly code look like drunken wanderers on the page! Gaaaaah....

 

This is a quick test to see if the raw BBcode editor is b0rken or not.

 

0 spaces
1 space
2 spaces
3 spaces
4 spaces
5 spaces
6 spaces
7 spaces
8 spaces
9 spaces
10 spaces
11 spaces
12 spaces
13 spaces
14 spaces
15 spaces
16 spaces

0 spaces
1 space
2 spaces
3 spaces
4 spaces
5 spaces
6 spaces
7 spaces
8 spaces
9 spaces
10 spaces
11 spaces
12 spaces
13 spaces
14 spaces
15 spaces
16 spaces

 

Seems broken on multiples of four.

 

-dZ.

 

Doubly broken on reply: spaces were coalesced.

Link to comment
Share on other sites

Now actually commenting on the meat... Cool stuff! Basically, if you have pieces of code that you know are mutually exclusive, you can overlay their RAM areas, to save on overall RAM usage. Still, it's up to you to ensure that two different variables in two different RAM segments are never actually needed at the same time.

 

How do you declare variables that are needed "everywhere"? I assume you must declare these before "RAMSTART"? And what about variables that are needed in two places, but not all? Do those have to fall back to "everywhere"?

 

Ah, you *could* do that; RAMSEG will take care of segmenting everything *after* you call RAMSTART.

 

Alternative, you could use the default "Global" segment, which will never be overlapped. That's what I do in my posted example: All variables following RAMSTART are "global" in the default segment. As soon as I call RAMSEG_START(), it will close that segment and create a new one, but append it to the global one. All calls to RAMSEG_START() after that will create overlapping segments, using the end of the global segment as the base.

 

 

BTW, one other observation: If you're using the 42K map, you're not guaranteed that two consecutive calls to SYSTEM will return contiguous words. (Likewise for SCRATCH if you have REQ_ECS=1.) The only way to ensure contiguous memory allocation is to ask for multiple bytes/words in a single call to SCRATCH or SYSTEM. For example:

 

.player_info SCRATCH 5
PLAYER_INFO STRUCT .player_info
@@Rank EQU $ + 0
@@ScoreRevs EQU $ + 1
@@Score EQU $ + 2
@@Lives EQU $ + 4
ENDS

 

To make this cleaner probably requires some support inside AS1600. But, it seems like some creative macros for defining structures might work OK even without special support. Thoughts?

 

Hum... I believe that the SYSTEM and SCRATCH macros are your old, "legacy" macros of CART.MAC. You superseded them with WORDARRAY and BYTEARRAY. Later, you put back SYSTEM and SCRATCH at my request because old code used it. Consequently, they don't use any of the fancy memory allocation in CART.MAC, just straight contiguous increments of on-board memory.

 

In any case, I meant to add that caveat to my comment: if you use anything else than SYSTEM and SCRATCH to allocate memory, results are not guaranteed because of the "fancy" non-contiguous allocation the other macros do.

 

-dZ.

Link to comment
Share on other sites

(PS. I found a really crappy way of hacking around the formatting issues... I dropped using the code tag, and instead, copy/pasted my text into GEdit, did a search and replace, replacing space characters with an "emspace" I copied out of an HTML 4.0 reference. I pasted the result back here and set the font to Courier. Ugly hack, but it worked.)

 

Well, I could do that. It's ugly, but if it works...

Link to comment
Share on other sites

Ah, you *could* do that; RAMSEG will take care of segmenting everything *after* you call RAMSTART.

 

Alternative, you could use the default "Global" segment, which will never be overlapped. That's what I do in my posted example: All variables following RAMSTART are "global" in the default segment. As soon as I call RAMSEG_START(), it will close that segment and create a new one, but append it to the global one. All calls to RAMSEG_START() after that will create overlapping segments, using the end of the global segment as the base.

 

If I've understood everything correctly, then, you'd have to put all the "global" variables ahead of all the overlapped variables. Is this correct?

 

Hum... I believe that the SYSTEM and SCRATCH macros are your old, "legacy" macros of CART.MAC. You superseded them with WORDARRAY and BYTEARRAY. Later, you put back SYSTEM and SCRATCH at my request because old code used it. Consequently, they don't use any of the fancy memory allocation in CART.MAC, just straight contiguous increments of on-board memory.

 

You know, I thought I had made SYSTEM/SCRATCH effectively into aliases of WORDARRAY/BYTEARRAY, but I did not. SCRATCH is limited to the Master Component's in-built 8-bit RAM, and SYSTEM is limited to the Master Component's in-built 16-bit RAM, so yeah, they will produce contiguous allocations.

Link to comment
Share on other sites

Ah, you *could* do that; RAMSEG will take care of segmenting everything *after* you call RAMSTART.

 

Alternative, you could use the default "Global" segment, which will never be overlapped. That's what I do in my posted example: All variables following RAMSTART are "global" in the default segment. As soon as I call RAMSEG_START(), it will close that segment and create a new one, but append it to the global one. All calls to RAMSEG_START() after that will create overlapping segments, using the end of the global segment as the base.

 

If I've understood everything correctly, then, you'd have to put all the "global" variables ahead of all the overlapped variables. Is this correct?

 

Yes, that is correct. I started working on fancy features like the ability to re-opened closed segments (like the global one) so that you can append to them, and support for "super-segments" each one containing overlapping segments.

 

I got very far into it, but I ran into some trouble. I decided to put all that aside for the moment, so only the basic stuff is there. I plan on going back to it when I return to P-Machinery v2.0.

 

Hum... I believe that the SYSTEM and SCRATCH macros are your old, "legacy" macros of CART.MAC. You superseded them with WORDARRAY and BYTEARRAY. Later, you put back SYSTEM and SCRATCH at my request because old code used it. Consequently, they don't use any of the fancy memory allocation in CART.MAC, just straight contiguous increments of on-board memory.

 

You know, I thought I had made SYSTEM/SCRATCH effectively into aliases of WORDARRAY/BYTEARRAY, but I did not. SCRATCH is limited to the Master Component's in-built 8-bit RAM, and SYSTEM is limited to the Master Component's in-built 16-bit RAM, so yeah, they will produce contiguous allocations.

 

Yes. So as long as you use SCRATCH and SYSTEM, you are safe. The next version of RAMSEG should take better care of using the best bits of CART.MAC.

 

-dZ.

Link to comment
Share on other sites

And what about variables that are needed in two places, but not all? Do those have to fall back to "everywhere"?

 

Ah! I missed that part of the question. Unfortunately, you'll have to fall back on "everywhere" right now. RAMSEG supports only "global" and "overlapped," nothing in between.

 

-dZ.

Link to comment
Share on other sites

  • 8 months later...

Now actually commenting on the meat... Cool stuff! Basically, if you have pieces of code that you know are mutually exclusive, you can overlay their RAM areas, to save on overall RAM usage. Still, it's up to you to ensure that two different variables in two different RAM segments are never actually needed at the same time.

 

How do you declare variables that are needed "everywhere"? I assume you must declare these before "RAMSTART"? And what about variables that are needed in two places, but not all? Do those have to fall back to "everywhere"?

 

(I admit I haven't peeked at the source yet.)

 

BTW, one other observation: If you're using the 42K map, you're not guaranteed that two consecutive calls to SYSTEM will return contiguous words. (Likewise for SCRATCH if you have REQ_ECS=1.) The only way to ensure contiguous memory allocation is to ask for multiple bytes/words in a single call to SCRATCH or SYSTEM. For example:

 

 

 

.player_info      SCRATCH  5

PLAYER_INFO       STRUCT  .player_info

@@Rank            EQU     $ + 0

@@ScoreRevs       EQU     $ + 1

@@Score           EQU     $ + 2

@@Lives           EQU     $ + 4

                  ENDS

 

 

To make this cleaner probably requires some support inside AS1600. But, it seems like some creative macros for defining structures might work OK even without special support. Thoughts?

 

I just wanted to revive this thread a bit because I re-implemented the RAMSEG library as part of my next generation P-Machinery framework, and I just realized that this particular issue has been made worse: SCRATCH and SYSTEM are now in fact aliases of BYTEARRAY and WORDARRAY, respectively.

 

I know I can come up with some ideas, but I was wondering what kind of "creative macro" you can think of that would help? I'm trying to define interfaces that fit within the mental model of the framework.

 

One idea I had was that of offering an explicit "record" structure that would encapsulate a similar layout like your example above, but I would like it to collect the size of the record from the field definitions themselves, rather than requiring the programmer to provide it a priori.

 

Something like,

 

FOO RECORD
RBYTE a 1
RBYTE b 1
RBYTE c 2
RWORD d 1
RWORD e 1
ENDREC

 

The macro "RECORD" would initialize a byte and word counter, and keep track of size of the fields, and when ENDREC is encountered, it would define a contiguous block of RAM for words and bytes, respectively.

 

However, I can't think of a way to retain the symbol names until the end. Any better ideas?

 

-dZ.

Link to comment
Share on other sites

SCRATCH and SYSTEM have been aliases of BYTEARRAY and WORDARRAY for a very long time.

 

In any case, you're trying to do something different than they do. I still have this haunting suspicion you should just write a perl script that takes a "game setup" and outputs an asm file that sets up everything for your game. :-) You're stretching AS1600 much further than it's intended to go. :-)

 

That said, for your record thingy, what if you just provide the label at the end, rather than the beginning?

Link to comment
Share on other sites

SCRATCH and SYSTEM have been aliases of BYTEARRAY and WORDARRAY for a very long time.

 

Hmm.. read upthread; they use exclusively on-board (Master Component) 8-bit Scratch or 16-bit System RAM, so they will guarantee contiguousness. ;)

 

I changed this when I ported CART.MAC into P-Machinery to grab RAM from any available segment of the size, just like BYTEARRAY and WORDARRAY do. This means that using say, SYSTEM, will attempt to allocate from 16-bit System RAM, Cartridge RAM, etc.

 

Therefore, like you suggested in your previous message, using a STRUCT to define a record will not guarantee contiguousness of all fields of a type, unless you allocate the full amount of all fields before hand.

 

In any case, you're trying to do something different than they do. I still have this haunting suspicion you should just write a perl script that takes a "game setup" and outputs an asm file that sets up everything for your game. :-) You're stretching AS1600 much further than it's intended to go. :-)

 

Heh. I'm sure it can take it, I'm not that smart. ;)

 

Seriously, I'll definitely consider that approach for "P-Machinery v3.0: Electric Boogaloo." :) As it is now, I'm having a hard enough time compiling and reconciling my ideas from Pac-Man, Christmas Carol, and some high-level concepts I've dreamt of, into a comprehensive framework; so changing the building mechanism and approach will just confuse me even more. I'm designing it as I go, so once it is completed, and its practicality proven, I can "upgrade" it by re-implementing it in any way, even as a new language (Hello, SPLINT!).

 

That said, for your record thingy, what if you just provide the label at the end, rather than the beginning?

 

Do you mean the label of the record or the fields? Because I was thinking of the latter as the problem...

 

-dZ.

Edited by DZ-Jay
Link to comment
Share on other sites

Hmm.. read upthread; they use exclusively on-board (Master Component) 8-bit Scratch or 16-bit System RAM, so they will guarantee contiguousness. ;)

Oh, I forgot that SCRATCH and SYSTEM do limit you to the built in memory. Good point.

 

 

In any case, you're trying to do something different than they do. I still have this haunting suspicion you should just write a perl script that takes a "game setup" and outputs an asm file that sets up everything for your game. :-) You're stretching AS1600 much further than it's intended to go. :-)

 

Heh. I'm sure it can take it, I'm not that smart. ;)

 

You're joking, right? I believe it'd actually be easier than what you're doing now. :-)

 

 

Seriously, I'll definitely consider that approach for "P-Machinery v3.0: Electric Boogaloo." :) As it is now, I'm having a hard enough time compiling and reconciling my ideas from Pac-Man, Christmas Carol, and some high-level concepts I've dreamt of, into a comprehensive framework; so changing the building mechanism and approach will just confuse me even more. I'm designing it as I go, so once it is completed, and its practicality proven, I can "upgrade" it by re-implementing it in any way, even as a new language (Hello, SPLINT!).

 

I was thinking something rather simpler. Just scan the input, passing through everything that appears between PROC/ENDP more or less directly to the output, but the stuff outside it you can interpret as you like from Perl.

 

That said, for your record thingy, what if you just provide the label at the end, rather than the beginning?

Do you mean the label of the record or the fields? Because I was thinking of the latter as the problem...

 

I was thinking the opening label, although I guess they're all problematic. You need to know the structure's size before you can allocate it, but you give the labels with the pieces of the size info.

 

Link to comment
Share on other sites

I was thinking something rather simpler. Just scan the input, passing through everything that appears between PROC/ENDP more or less directly to the output, but the stuff outside it you can interpret as you like from Perl.

 

Yes, that'll work, and it'll be easier. Unfortunately, it'll add another layer of dependencies to the build process for programmers using P-Machinery. My assumption is that not everybody knows what Perl even is.

 

I guess I could "bite the bullet" and make Perl a pre-requisite for using the framework. (Perhaps even include an interpreter as part of the distribution.) I could include a makefile that would call the Perl "pre-pre-processor" and then the assembler.

 

It just seems like such a bigger thing then... I'll think about it.

 

I was thinking the opening label, although I guess they're all problematic. You need to know the structure's size before you can allocate it, but you give the labels with the pieces of the size info.

 

Well, no worries. I wanted to relieve the programmer from the mental burden of having to keep track of memory segment boundaries and all that. I was even dreaming up some sort of mechanism where the framework could keep track of the holes left, and re-use them for smaller sized blocks; like some sort of memory "de-frag."

 

However, I don't think it'll be worth all that effort, versus just placing a caveat in the framework documentation that asks the programmer to pre-allocate a block of memory for records, if he expects the fields to be contiguous. I can provide a wrapper macro to make that cleaner, as you suggested.

 

Thanks for the help. :)

-dZ.

Link to comment
Share on other sites

OK, I think I got something. This is the first draft of the interface. Any suggestions as to the mnemonics is appreciated. The macro "RECORD" replaces "STRUCT" and it takes two arguments: the size of all 8-bit fields and 16-bit fields, respectively.

 


FOO       RECORD 4, 2
         ; Define three 8-bit fields
RBYTE     field1  1
RBYTE     field2  1
RBYTE     field3  2

         ; Define two 16-bit fields
RWORD     field4  1
RWORD     field5  1
         ENDREC

 

 

"RBYTE" and "RWORD" represent "record byte" and "record word" field definition. Those names could use some work.

Edited by DZ-Jay
Link to comment
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.
Note: Your post will require moderator approval before it will be visible.

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...