Jump to content
Sign in to follow this  
jacobus

Switching off ROM in an XL (using upper 16K RAM)

Recommended Posts

This problem is really driving me nuts. I can only assume I am missing something really stupid and obvious, but I sick and tired of banging my head against it…

 

What is the standard “correct/failsafe/easy” way to activate the upper 16K bank or RAM on an XL ? I’ve been trying to do this within Quick and keep running into a brick wall. I use Quick’s copy command BMOVE(Source,Dest,Length) to copy the ROM to a safe place in memory, then I turn off interrupts (via NMIEN=0 and IRQEN=0) and then disable ROM by setting bit 0 of PORTB to 0. Then I copy the ROM code back to its proper location. At some point during this operation the system crashes – with the ROM only partially restored.

 BMOVE($C000,$600,$1000)
 BMOVE($D800,$1600,$2800)

 EOR(PORTB,1,C)
 NMIEN=0
 IRQEN=0
 PORTB=C

 BMOVE($600,$C000,$1000)
 BMOVE($1600,$D800,$2800)

What the heck am I doing wrong???

 

 

Share this post


Link to post
Share on other sites

Are you sure that memory starting at $600 is free? this means, you don't have a DOS installed. I would try with some other temporary location, e.g. $2600, or $3000.

 

I don't know "Quick". In assembly I never bothered with IRQEN, but just disabled interrupts in the processor with "sei".

Share this post


Link to post
Share on other sites

The safest way is to disable all interrupts (maskable and non-maskable).

1. You already disable NMIs

2. Disable maskable interrupts by using the SEI instruction

3. After copying ROM to RAM, re-enable maskable interrupts by using the CLI instruction.

Share this post


Link to post
Share on other sites

re bakra's post:

 

and then re-enable NMIs again....

Edited by sanny

Share this post


Link to post
Share on other sites

For a quick & dirty switchover you could get away with just doing SEI and setting NMIEN to 00, e.g. to fetch or store a bit of data before enabling the OS Rom again.

Longer term you'd probably want to supply your own IRQ and NMI handlers. Just use an entry handler that switches the OS Rom back in, pushes some PC and P info to the stack so that it gets control back, jumps to the normal OS handler then switches the OS back off once it gets control back at the exit point.

Note that for DLIs though, any extra overhead will probably have detrimental effects, but the handler code to deal with DLIs is only 3 instructions long so easily catered for: BIT NMIST / BPL xxxx / JMP ($200)

 

When dealing with PORTB bits it's best practice to use AND/OR masking rather than setting the register to an absolute value. That means you preserve the state of other bits which control stuff like Basic and extended Ram @ $4000.

  • Like 1

Share this post


Link to post
Share on other sites

Thanks to everyone for their replies, the assistance is very much appreciated! Sadly, I'm still having issues...

 

Things I've tired/eliminated

 

-Memory at $600 is unused (I've tested this without setting PORTB and have no issues)

-I've added SEI and CLI instructions to the code - does not help

-Tried with and without setting IRQEN

-I am masking the PORTB to set it properly

 

I've attached a sample .XEX compiled from the attached code. As mentioned if I remove the statement that sets PORTB, the code runs fine. No matter what I do beforehand, setting PORTB is lethal to the program.

 

 

 

 

Quick-Sourcetext
D1:PORTB.QIK   
----------------
Length: 
$0DF6

Free  : $6975
----------------

************************************
************************************

BYTE
 [
  PORTB=54017
  SDMA=559
  NMIEN=54286
  IRQEN=53774
  CONSOL=53279

  A,B,C
 ]

 WORD
 [
  MEM
 ]

************************************
*** MAIN LOOP ***
************************************

MAIN

 B=NMIEN

 PRINT("Press START to begin")
 REPEAT
 UNTIL CONSOL=6

 BMOVE($C000,$600,$1000)
 BMOVE($D800,$1600,$27FF)
 PRINT("ROM to RAM copy complete")

 NMIEN=0
 PRINT("NMIEN disabled")

 INLINE
 [
  120
 ]
 PRINT("SEI issued")

 EOR(PORTB,1,C)
 PRINT("")
 PRINT("Press START to set PORTB=",C)
 REPEAT
 UNTIL CONSOL=6

 PORTB=C
 PRINT("PORTB set")

 BMOVE($600,$C000,$1000)
 BMOVE($1600,$D800,$27FF)
 PRINT("ROM code restore complete")

 INLINE
 [
  88
 ]
 PRINT("CLI issued")

 NMIEN=B
 PRINT("NMIEN restored")

 *test
 CLR(192,4)
 A=0
 MEM=49152
 REPEAT
  PEEK(MEM,B)
  IF B>0
   A=1
  ENDIF
  ADD(MEM,1,MEM)
 UNTIL MEM=50176

 IF A=0
  PRINT("Success!")
 ELSE
  PRINT("Failure")
 ENDIF

ENDMAIN

************************************
************************************
************************************

 

 

 

 

PORTB.XEX

Share this post


Link to post
Share on other sites

In your "*test", you are destroying the memory area 49152...50176. This is not good :-)

 

And note, that you cannot get the current contents of NMIEN by reading it.

  • Like 1

Share this post


Link to post
Share on other sites

$27FF should be $2800. You're not saving and restoring the MSB of the IRQ vector.

 

Also: are you sure the runtime does not somehow depend on RAM under the OS?

  • Like 2

Share this post


Link to post
Share on other sites
sanny, on 07 Jul 2017 - 11:32 AM, said:

In your "*test", you are destroying the memory area 49152...50176. This is not good :-)

 

And note, that you cannot get the current contents of NMIEN by reading it.

I changed it to overwrite the International Charset at CC00 - same result :-(

 

Good point about NMIEN, I set that to $40 and tried again - same result.

 

 

flashjazzcat, on 07 Jul 2017 - 12:18 PM, said:

$27FF should be $2800. You're not saving and restoring the MSB of the IRQ vector.

 

Also: are you sure the runtime does not somehow depend on RAM under the OS?

Hi Jon

 

I wanted to be sure I was not over-wring the first byte of page 0, change to $2800 - same result

 

Good question about the runtime - I don't think so - the compiled program works fine on a 48K 800, and the manual states that the first part of that RAM (C000 to CBFF) is used by the integrated editor.

 

 

 

I beginning to think as Jon hints that it may be something with my environment instead of something boneheaded on my part. Interestingly however, I can use the ROM switching code provided (page 212 I think) in the Revised Mapping the Atari to accomplish this - if - I run it as an executable before launching my program. Any attempt to incorporate the MtA code directly into my program fails in the same why this example does.

Share this post


Link to post
Share on other sites

How much RAM is on the test machine? I ask because I took a look at one of the Quick demos and the object file is compiled to $4100. So any inadvertent writes to the bank select bits of PORTB on a machine with extended RAM will probably bank out the program itself.

Share this post


Link to post
Share on other sites

I also see an EOR to PORTB.

Doing that assumes it's in a known state which is fair enough, but it's a better idea to save/restore on program entry/exit the PORTB state and in the meantime use AND/OR to do bitsettings.

Share this post


Link to post
Share on other sites

Can't log "PORTB set" before actually doing the copy back up to high RAM. That's not going to work since there's no OS up there to print text with yet.

 

Also, side note, not sure why the runtime hardcodes the resident E: put vector instead of using ICPTL/H....

  742: 56: 16 | A=50 X=00 Y=F3 (   I  ) | 41C9: 69 00             ADC #$00
  742: 56: 19 | A=50 X=00 Y=F3 (   I  ) | 41CB: 48                PHA
  742: 56:102 | A=50 X=00 Y=F3 (   I  ) | 41CC: 98                TYA
  742: 56:104 | A=F3 X=00 Y=F3 (N  I  ) | 41CD: 48                PHA
  742: 56:107 | A=F3 X=00 Y=F3 (N  I  ) | 41CE: 60                RTS
  742: 56:113 | A=F3 X=00 Y=F3 (N  I  ) | 50F4: AD 1F D0          LDA CONSOL
  742: 57:  3 | A=07 X=00 Y=F3 (   I  ) | 50F7: C9 06             CMP #$06
  742: 57:  5 | A=07 X=00 Y=F3 (   I C) | 50F9: F0 03             BEQ $50FE
  742: 57:  7 | A=07 X=00 Y=F3 (   I C) | 50FB: 4C F4 50          JMP $50F4
+ Last 4 insns repeated 6479 times
  746:  0:  7 | A=07 X=00 Y=F3 (   I C) | 50F4: AD 1F D0          LDA CONSOL
  746:  0: 11 | A=06 X=00 Y=F3 (   I C) | 50F7: C9 06             CMP #$06
  746:  0: 13 | A=06 X=00 Y=F3 (   IZC) | 50F9: F0 03             BEQ $50FE
  746:  0: 16 | A=06 X=00 Y=F3 (   IZC) | 50FE: AD 82 B0          LDA $B082
  746:  0: 20 | A=FE X=00 Y=F3 (N  I C) | 5101: 8D 01 D3          STA PORTB
  746:  0: 24 | A=FE X=00 Y=F3 (N  I C) | 5104: A0 00             LDY #$00
+ 746:  0: 27 | A=FE X=00 Y=00 (   IZC) | 5106: 20 03 41          JSR $4103
  746:  0: 54 | A=51 X=00 Y=00 (   I C) | 4189: 85 87             STA $87
  746:  0: 58 | A=51 X=00 Y=00 (   I C) | 418B: 84 B5             STY $B5
  746:  0: 61 | A=51 X=00 Y=00 (   I C) | 418D: A0 01             LDY #$01
  746:  0: 63 | A=51 X=00 Y=01 (   I C) | 418F: B1 86             LDA ($86),Y  ;$5109
  746:  0: 68 | A=04 X=00 Y=01 (   I C) | 4191: C9 FF             CMP #$FF
  746:  0: 70 | A=04 X=00 Y=01 (   I  ) | 4193: F0 25             BEQ $41BA
  746:  0: 72 | A=04 X=00 Y=01 (   I  ) | 4195: C9 FE             CMP #$FE
  746:  0: 74 | A=04 X=00 Y=01 (   I  ) | 4197: F0 36             BEQ $41CF
  746:  0: 76 | A=04 X=00 Y=01 (   I  ) | 4199: C9 01             CMP #$01
  746:  0: 78 | A=04 X=00 Y=01 (   I C) | 419B: F0 42             BEQ $41DF
  746:  0: 80 | A=04 X=00 Y=01 (   I C) | 419D: C9 02             CMP #$02
  746:  0: 82 | A=04 X=00 Y=01 (   I C) | 419F: F0 52             BEQ $41F3
  746:  0: 84 | A=04 X=00 Y=01 (   I C) | 41A1: C9 03             CMP #$03
  746:  0: 86 | A=04 X=00 Y=01 (   I C) | 41A3: F0 74             BEQ $4219
  746:  0: 88 | A=04 X=00 Y=01 (   I C) | 41A5: C9 04             CMP #$04
  746:  0: 90 | A=04 X=00 Y=01 (   IZC) | 41A7: D0 03             BNE $41AC
  746:  0: 92 | A=04 X=00 Y=01 (   IZC) | 41A9: 4C 4C 42          JMP $424C
  746:  0: 95 | A=04 X=00 Y=01 (   IZC) | 424C: C8                INY
  746:  0: 97 | A=04 X=00 Y=02 (   I C) | 424D: B1 86             LDA ($86),Y  ;$510A
  746:  0:102 | A=50 X=00 Y=02 (   I C) | 424F: F0 0B             BEQ $425C
  746:  0:104 | A=50 X=00 Y=02 (   I C) | 4251: 84 90             STY $90
- 746:  0:107 | A=50 X=00 Y=02 (   I C) | 4253: 20 8C 42          JSR $428C
    746:  0:113 | A=50 X=00 Y=02 (   I C) | 428C: 85 92             STA $92
    746:  1:  2 | A=50 X=00 Y=02 (   I C) | 428E: A5 B5             LDA $B5
    746:  1:  5 | A=00 X=00 Y=02 (   IZC) | 4290: D0 0B             BNE $429D
    746:  1:  7 | A=00 X=00 Y=02 (   IZC) | 4292: AD 07 E4          LDA $E407
    746:  1: 11 | A=00 X=00 Y=02 (   IZC) | 4295: 48                PHA
    746:  1: 14 | A=00 X=00 Y=02 (   IZC) | 4296: AD 06 E4          LDA $E406
    746:  1: 18 | A=FF X=00 Y=02 (N  I C) | 4299: 48                PHA
    746:  1: 21 | A=FF X=00 Y=02 (N  I C) | 429A: A5 92             LDA $92
    746:  1: 24 | A=50 X=00 Y=02 (   I C) | 429C: 60                RTS
    746:  1: 32 | A=50 X=00 Y=02 (   I C) | 0100: 00                BRK

  • Like 2

Share this post


Link to post
Share on other sites
flashjazzcat, on 07 Jul 2017 - 2:01 PM, said:

How much RAM is on the test machine? I ask because I took a look at one of the Quick demos and the object file is compiled to $4100. So any inadvertent writes to the bank select bits of PORTB on a machine with extended RAM will probably bank out the program itself.

It's a stock 64K 800XL. I have some experience bank switching within Quick (the cartridge version of Dungeon Hunt) so I'm pretty sure that's not he problem either.

 

The good news is that I was able to add the small program from Mapping the Atari (attached) as the first segment in my executable so my (compiled) program can use the extra RAM. Bad news is that I can't get the ROM/RAM switcher to work within my development environment - making testing a little more challenging and time consuming.

 

phaeron, on 08 Jul 2017 - 12:59 AM, said:

 

Can't log "PORTB set" before actually doing the copy back up to high RAM. That's not going to work since there's no OS up there to print text with yet.

 

 

Right, yes, thank you - I knew that! Really! (cue sound of head being banged against desk). :-)

 

I did try removing that line in hopes that the call itself was causing my issues - sadly not. Thank you anyway!

64K.xex

Share this post


Link to post
Share on other sites

Note: as mentioned earlier, NMIEN is write-only, so you can't push its initial value onto the stack and reinstate it later (which is what's being attempted in 64K.XEX).

Share this post


Link to post
Share on other sites

For NMIEN you can usually safely assume that only VBlank is enabled.

 

DLI will only be used by the OS under these circumstances:

- in the Self Test system, user code won't be running anyway and exit always means coldstart.

- with text fine scrolling enabled for E: (POKE 622,128 : GR. 0 in Basic). Later XL Rom revisions use the DLI on the last text line to prevent inadvertant display of data seen by the 4K wraparound of Antic DMA, by setting the PF1 colour to same as PF2.

 

DLIs might be in use by user code but it's your program running so it should know anyhow.

There's stuff like SpartaDos time display, unsure if it uses DLIs (probably not).

Share this post


Link to post
Share on other sites

@jacubus: do you really need copy of ROM in RAM memory? What is your use-case?

Here you can find some details about switching ROM off: http://atariage.com/forums/topic/209369-display-lists-and-double-buffering/?p=2705155

There is another thread about uploading data under ROM (and to EXT memory): http://atariage.com/forums/topic/212907-possible-to-load-to-high-memory-d800-from-xex/

Share this post


Link to post
Share on other sites

Kind of OUCH! While your programming is good, your<and coincidentally mine and everyone else's knowledge of this language> is lacking. Specifically things like where the run time package resides. I would bet among other suspects that you are overwriting either the run time or your own code. I saw no directives for where to start your code and I know next to nothing about Quick, but I do know a bit about languages and their implementations. I hope you can see what I am saying here, if Quick puts your code in low memory, a common practice, your BMOVE could be at $700 with no DOS booted. Your memory move overwrites you code as soon as it gets to $700! Also the run time code, the actual code Quick uses to perform functions, may be in low memory.

 

There are a couple of way to tackle the problem that just require you do a few extra steps. For example, reserve 16k by changing RAMTOP and do a graphics call, then use that for the target of your ROM save. You may be able to just dimension an ARRAY in Quick that will give you 16k of safe storage. I really don't know since it has been 45 years since I last had a German language class so I can't decipher the .PDF manual. *BUT* then if you reserve memory using one of the techniques I described, you shouldn't need any new info.

 

Flash said he saw code placement directives in some sample code. You should be able to make your code start at $600 + 16k to get it to work almost as written. Lot's of ways to corner the problem but it is tough without a good memory map of the language.

Share this post


Link to post
Share on other sites
flashjazzcat, on 11 Jul 2017 - 07:36 AM, said:

Note: as mentioned earlier, NMIEN is write-only, so you can't push its initial value onto the stack and reinstate it later (which is what's being attempted in 64K.XEX).

I have access to the source code, so I'll be sure to change that before going live with this project - thanks!

Share this post


Link to post
Share on other sites
Rybags, on 11 Jul 2017 - 09:03 AM, said:Rybags, on 11 Jul 2017 - 09:03 AM, said:

For NMIEN you can usually safely assume that only VBlank is enabled.

 

DLI will only be used by the OS under these circumstances:

- in the Self Test system, user code won't be running anyway and exit always means coldstart.

- with text fine scrolling enabled for E: (POKE 622,128 : GR. 0 in Basic). Later XL Rom revisions use the DLI on the last text line to prevent inadvertant display of data seen by the 4K wraparound of Antic DMA, by setting the PF1 colour to same as PF2.

 

DLIs might be in use by user code but it's your program running so it should know anyhow.

There's stuff like SpartaDos time display, unsure if it uses DLIs (probably not).

Thanks - I'll be sure to set it manually from now on.

 

bob_er, on 11 Jul 2017 - 11:28 AM, said:bob_er, on 11 Jul 2017 - 11:28 AM, said:

@jacubus: do you really need copy of ROM in RAM memory? What is your use-case?

Here you can find some details about switching ROM off: http://atariage.com/forums/topic/209369-display-lists-and-double-buffering/?p=2705155

There is another thread about uploading data under ROM (and to EXT memory): http://atariage.com/forums/topic/212907-possible-to-load-to-high-memory-d800-from-xex/

I need a bit more storage for some data - even having access to the 1K that holds the alternate character set would make a big difference.

 

 

ricortes, on 11 Jul 2017 - 2:37 PM, said:ricortes, on 11 Jul 2017 - 2:37 PM, said:

Kind of OUCH! While your programming is good, your<and coincidentally mine and everyone else's knowledge of this language> is lacking. Specifically things like where the run time package resides. I would bet among other suspects that you are overwriting either the run time or your own code. I saw no directives for where to start your code and I know next to nothing about Quick, but I do know a bit about languages and their implementations. I hope you can see what I am saying here, if Quick puts your code in low memory, a common practice, your BMOVE could be at $700 with no DOS booted. Your memory move overwrites you code as soon as it gets to $700! Also the run time code, the actual code Quick uses to perform functions, may be in low memory.

 

There are a couple of way to tackle the problem that just require you do a few extra steps. For example, reserve 16k by changing RAMTOP and do a graphics call, then use that for the target of your ROM save. You may be able to just dimension an ARRAY in Quick that will give you 16k of safe storage. I really don't know since it has been 45 years since I last had a German language class so I can't decipher the .PDF manual. *BUT* then if you reserve memory using one of the techniques I described, you shouldn't need any new info.

 

Flash said he saw code placement directives in some sample code. You should be able to make your code start at $600 + 16k to get it to work almost as written. Lot's of ways to corner the problem but it is tough without a good memory map of the language.

Run time sits at $4100 and the program itself starts at $5000 (for a maximum of 20K) As long as I don't need DOS (and I don't) I have free access to memory from $400 to $40FF as well as $A000 to $AFFF (additionally I can take up space at the end of program RAM and even part of the variable table)

 

I have to believe the issue is with the environment left by the compiler. As mentioned once I compile to an executable and make the 64K.XEX the first segment, it runs fine.

 

For reference, I've attached a PDF of the English manual. I am working on an revision with more complete instructions and examples. The memory map is fairly well documented, a heck of a lot of experimentation on my part has revealed more.

 

Edited by jacobus
  • Like 1

Share this post


Link to post
Share on other sites

Still not quite enough info. Just an example of what I am trying to get across. I load your problem into Altirra with the debugger & CPU trace on. Immediately I get in the bottom window

EXE: Loading program 0006-1156 to 4100-5250

EXE: Loading program 115B-115C to 02E0=02E1

EXE: Launching at 5000

 

So the initial load uses memory up to $5250 and you say your program starts at $5000? Does the program compile the run time and include your program as part of a contiguous block or is the run time loading to $5250 and your program is stepping on it at $5000? It sort of makes sense but then you assumed you could use the memory from $600 to $600 + 16k but the program is actually loading at $4100 up even though your run time is loaded before it.

 

I kind of have to give up at this point not for lack of empathy but lack of information and declining brain cells. I am just getting more confused as I get more confident nobody knows what is going on because of lack of documentation. I tend to think Altirra doesn't lie. :)

Share this post


Link to post
Share on other sites

While I'm resting<sic :) > just a few other oddities that I think should be pointed out. I didn't see where your saved the processor status. You probably should have the code block something like

[8 120] at the start and it should be restored with a [$28] somewhere. Just needs some more stuff to make it work and be bullet proof. I've never written a ROM to RAM switch myself so just guessing.

Share this post


Link to post
Share on other sites

In case you just need more space for your data - it sounds like you don't need ROM copy at all (or at least this is my understanding).

If so here you can find some details about switching ROM off: http://atariki.krap.pl/index.php/Programowanie:_Jak_wy%C5%82%C4%85czy%C4%87_ROM_systemu.

Site is in polish, so please use your favourite translator (but at least code is understandable).

Before call 'system_off' please synchronise with VBLANK using this code:

lda $14

cmp $14

beq *-2

jsr system_off

This is to be sure, that you don't get any NMI interrupt in the middle of switching off routine.

After that you have RAM available up to $CFFF and $D800-$FFF9.

Share this post


Link to post
Share on other sites
ricortes, on 11 Jul 2017 - 7:04 PM, said:

Still not quite enough info. Just an example of what I am trying to get across. I load your problem into Altirra with the debugger & CPU trace on. Immediately I get in the bottom window

EXE: Loading program 0006-1156 to 4100-5250

EXE: Loading program 115B-115C to 02E0=02E1

EXE: Launching at 5000

 

So the initial load uses memory up to $5250 and you say your program starts at $5000? Does the program compile the run time and include your program as part of a contiguous block or is the run time loading to $5250 and your program is stepping on it at $5000? It sort of makes sense but then you assumed you could use the memory from $600 to $600 + 16k but the program is actually loading at $4100 up even though your run time is loaded before it.

 

I kind of have to give up at this point not for lack of empathy but lack of information and declining brain cells. I am just getting more confused as I get more confident nobody knows what is going on because of lack of documentation. I tend to think Altirra doesn't lie. :)

The runtime consists of a number of routines that extend the basic functionality of Quick's command set. They are loaded to $4100 and are called from the main Quick program as needed. The actual Quick program loads and runs from $5000.

 

Limited experimentation suggests that a Quick program that does not call any of these extended capabilities can do without the runtime altogether.

 

ricortes, on 11 Jul 2017 - 9:27 PM, said:

While I'm resting<sic :) > just a few other oddities that I think should be pointed out. I didn't see where your saved the processor status. You probably should have the code block something like

[8 120] at the start and it should be restored with a [$28] somewhere. Just needs some more stuff to make it work and be bullet proof. I've never written a ROM to RAM switch myself so just guessing.

I do issue SEI before setting PORTB. Thanks for the tip about processor status - I tried PHP / PLA as well - no dice!

 

thanks very much for the suggestions - sadly it's still blowing up on me.

Share this post


Link to post
Share on other sites
bob_er, on 12 Jul 2017 - 06:36 AM, said:

In case you just need more space for your data - it sounds like you don't need ROM copy at all (or at least this is my understanding).

If so here you can find some details about switching ROM off: http://atariki.krap.pl/index.php/Programowanie:_Jak_wy%C5%82%C4%85czy%C4%87_ROM_systemu.

Site is in polish, so please use your favourite translator (but at least code is understandable).

Before call 'system_off' please synchronise with VBLANK using this code:

lda $14

cmp $14

beq *-2

jsr system_off

This is to be sure, that you don't get any NMI interrupt in the middle of switching off routine.

After that you have RAM available up to $CFFF and $D800-$FFF9.

I still need the functionality of the ROM - I just wanted to be able to take advantage of some of the unused portions - such as the International Character set space.

 

Thanks for the link - I checked it out and with my limited assembly skills the code seems to be doing everything I do with the exception of updating the NMI vectors - which since I'm not changing anything in the ROM, should not need to be updated.

 

The code you list here - it looks like it compares the value of $14 (APPMHI) with itself and then calls the routine stored at $FFFE - is that correct? If I have VBI's already disabled, is this required?

 

thank you!

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.
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...
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...