mirror of
https://github.com/Spritetm/minimacplus.git
synced 2024-12-28 18:29:25 +00:00
Speedups because direct pc-rel read/write, fix sound a bit
This commit is contained in:
parent
feaa2434ea
commit
ea1ed9972a
@ -231,8 +231,11 @@ static void IRAM_ATTR displayTask(void *arg) {
|
||||
//No need for update
|
||||
yend=342;
|
||||
} else {
|
||||
//Only copy changed bits of data to changebuffer
|
||||
memcpy(oldImg+(ystart*64), myData+(ystart*64), (yend-ystart)*64);
|
||||
|
||||
ystart=(ystart*32)/SCALE_FACT-1;
|
||||
yend=(yend*32)/SCALE_FACT+1;
|
||||
yend=(yend*32)/SCALE_FACT+2;
|
||||
if (ystart<0) ystart=0;
|
||||
printf("Changed %d to %d\n", ystart, yend);
|
||||
}
|
||||
@ -248,7 +251,7 @@ static void IRAM_ATTR displayTask(void *arg) {
|
||||
uint8_t *p=&img[1];
|
||||
for (int j=ystart; j<yend; j++) {
|
||||
for (int i=0; i<320; i++) {
|
||||
int v=findPixelVal(myData, i, j);
|
||||
int v=findPixelVal(oldImg, i, j);
|
||||
*p++=(v&0xff);
|
||||
*p++=(v>>8);
|
||||
}
|
||||
|
@ -48,11 +48,19 @@ void sndTask(void *arg) {
|
||||
int sndPush(uint8_t *data, int volume) {
|
||||
while (!sndDone()) usleep(1000);
|
||||
myVolume=volume;
|
||||
for (int i=0; i<370; i++) {
|
||||
buf[wp]=*data;
|
||||
data+=2;
|
||||
wp++;
|
||||
if (wp>=BUFLEN) wp=0;
|
||||
if (volume) {
|
||||
for (int i=0; i<370; i++) {
|
||||
buf[wp]=*data;
|
||||
data+=2;
|
||||
wp++;
|
||||
if (wp>=BUFLEN) wp=0;
|
||||
}
|
||||
} else {
|
||||
//muted
|
||||
for (int i=0; i<370; i++) {
|
||||
buf[wp++]=128;
|
||||
if (wp>=BUFLEN) wp=0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -7,14 +7,15 @@ COMPONENT_SRCDIRS := . musashi
|
||||
|
||||
MUSASHI_GEN_SRC := musashi/m68kops_pre.c musashi/m68kopac.c musashi/m68kopdm.c musashi/m68kopnz.c
|
||||
MUSASHI_GEN_OBJ := $(MUSASHI_GEN_SRC:%.c=%.o)
|
||||
COMPONENT_OBJS := musashi/m68kops_pre.o musashi/m68kopac.o musashi/m68kopdm-iram.o musashi/m68kopnz.o musashi/m68kcpu.o emu.o \
|
||||
COMPONENT_OBJS := musashi/m68kops_pre.o musashi/m68kopac.o musashi/m68kopdm-iram.o musashi/m68kopnz.o musashi/m68kcpu-iram.o emu.o \
|
||||
iwm.o via.o rtc.o ncr.o scc.o mouse.o
|
||||
|
||||
#nothing in iram: 16%
|
||||
#nothing in iram: 1240000
|
||||
#ac nz in iram: 19%
|
||||
#dm nz in iram: 19%
|
||||
#dm nz in iram: ng
|
||||
#ac dm in iram: 23%
|
||||
#dm in iram: 23%
|
||||
#dm in iram: 1709000
|
||||
#cpu in iram: 1278000
|
||||
|
||||
#-O3:
|
||||
#nothing: 13%
|
||||
@ -33,8 +34,9 @@ COMPONENT_EXTRA_CLEAN := $(addprefix $(COMPONENT_PATH)/musashi/,$(MUSASHI_GEN_SR
|
||||
|
||||
#musashi/m68kops_pre.o: CFLAGS += -O3
|
||||
#musashi/m68kopac.o: CFLAGS += -O3
|
||||
#musashi/m68kopdm.o: CFLAGS += -O3
|
||||
musashi/m68kopdm.o: CFLAGS += -O3
|
||||
#musashi/m68kopnz.o: CFLAGS += -O3
|
||||
musashi/m68kcpu.o: CFLAGS += -O3
|
||||
|
||||
emu.o: CFLAGS += -O3
|
||||
|
||||
|
@ -441,6 +441,22 @@ void m68k_write_memory_32(unsigned int address, unsigned int value) {
|
||||
}
|
||||
#endif
|
||||
|
||||
unsigned char *m68k_pcbase=NULL;
|
||||
|
||||
void m68k_pc_changed_handler_function(unsigned int address) {
|
||||
// printf("m68k_pc_changed_handler_function %x\n", address);
|
||||
const MemmapEnt *mmEnt=getMmmapEnt(address);
|
||||
if (mmEnt->memAddr) {
|
||||
uint8_t *p;
|
||||
p=(uint8_t*)MMAP_RAM_PTR(mmEnt, address);
|
||||
m68k_pcbase=p-address;
|
||||
} else {
|
||||
printf("PC not in mem!\n");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Should be called every second.
|
||||
void printFps() {
|
||||
struct timeval tv;
|
||||
@ -449,7 +465,7 @@ void printFps() {
|
||||
if (oldtv.tv_sec!=0) {
|
||||
long msec=(tv.tv_sec-oldtv.tv_sec)*1000;
|
||||
msec+=(tv.tv_usec-oldtv.tv_usec)/1000;
|
||||
// printf("Speed: %d%%\n", (int)(100000/msec));
|
||||
printf("Speed: %d%%\n", (int)(100000/msec));
|
||||
// printf("Mem free: %dKiB 8-bit, %dKiB 32-bit\n", xPortGetFreeHeapSizeCaps(MALLOC_CAP_8BIT)/1024, xPortGetFreeHeapSizeCaps(MALLOC_CAP_32BIT)/1024);
|
||||
}
|
||||
oldtv.tv_sec=tv.tv_sec;
|
||||
@ -458,7 +474,7 @@ void printFps() {
|
||||
|
||||
void tmeStartEmu(void *rom) {
|
||||
int ca1=0, ca2=0;
|
||||
int x, m=0, frame=0;
|
||||
int x, frame=0;
|
||||
int cyclesPerSec=0;
|
||||
macRom=rom;
|
||||
ramInit();
|
||||
@ -473,6 +489,7 @@ void tmeStartEmu(void *rom) {
|
||||
viaSet(VIA_PORTB, (1<<3));
|
||||
sccInit();
|
||||
printf("Initializing m68k...\n");
|
||||
m68k_pc_changed_handler_function(0x0);
|
||||
m68k_init();
|
||||
printf("Setting CPU type and resetting...");
|
||||
m68k_set_cpu_type(M68K_CPU_TYPE_68000);
|
||||
@ -484,27 +501,24 @@ void tmeStartEmu(void *rom) {
|
||||
printf("Done! Running.\n");
|
||||
while(1) {
|
||||
for (x=0; x<8000000/60; x+=1000) {
|
||||
for (int i=0; i<10; i++) {
|
||||
m68k_execute(100);
|
||||
for (int l=0; l<10; l++) {
|
||||
viaStep(1); //should run at 783.36KHz
|
||||
sccTick();
|
||||
}
|
||||
}
|
||||
m68k_execute(1000);
|
||||
viaStep(100); //should run at 783.36KHz
|
||||
sccTick(100);
|
||||
|
||||
int r=mouseTick();
|
||||
if (r&MOUSE_BTN) viaClear(VIA_PORTB, (1<<3)); else viaSet(VIA_PORTB, (1<<3));
|
||||
if (r&MOUSE_QXB) viaClear(VIA_PORTB, (1<<4)); else viaSet(VIA_PORTB, (1<<4));
|
||||
if (r&MOUSE_QYB) viaClear(VIA_PORTB, (1<<5)); else viaSet(VIA_PORTB, (1<<5));
|
||||
sccSetDcd(SCC_CHANA, r&MOUSE_QXA);
|
||||
sccSetDcd(SCC_CHANB, r&MOUSE_QYA);
|
||||
m=0;
|
||||
|
||||
//Sound handler keeps track of real time, if its buffer is empty we should be done with the video frame.
|
||||
if (sndDone()) break;
|
||||
if (x>(8000000/120) && sndDone()) break;
|
||||
}
|
||||
cyclesPerSec+=x;
|
||||
dispDraw(macFb[video_remap?1:0]);
|
||||
sndPush(macSnd[audio_remap?1:0], audio_en?audio_volume:0);
|
||||
localtalkTick();
|
||||
// localtalkTick();
|
||||
frame++;
|
||||
ca1^=1;
|
||||
viaControlWrite(VIA_CA1, ca1);
|
||||
@ -513,8 +527,8 @@ void tmeStartEmu(void *rom) {
|
||||
viaControlWrite(VIA_CA2, ca2);
|
||||
rtcTick();
|
||||
frame=0;
|
||||
printFps();
|
||||
printf("%d Hz\n", cyclesPerSec);
|
||||
// printFps();
|
||||
// printf("%d Hz\n", cyclesPerSec);
|
||||
cyclesPerSec=0;
|
||||
}
|
||||
}
|
||||
@ -532,17 +546,19 @@ void sccIrq(int req) {
|
||||
|
||||
|
||||
void viaCbPortAWrite(unsigned int val) {
|
||||
int oldRomRemap=rom_remap;
|
||||
static int writes=0;
|
||||
if ((writes++)==0) val=0x67;
|
||||
printf("VIA PORTA WRITE %x\n", val);
|
||||
video_remap=(val&(1<<6))?1:0;
|
||||
rom_remap=(val&(1<<4))?1:0;
|
||||
regenMemmap(rom_remap);
|
||||
audio_remap=(val&(1<<3))?0:1;
|
||||
if (oldRomRemap!=rom_remap) printf("ROM REMAP %d\n", rom_remap);
|
||||
iwmSetHeadSel(val&(1<<5));
|
||||
audio_remap=(val&(1<<3))?1:0;
|
||||
audio_volume=(val&7);
|
||||
iwmSetHeadSel(val&(1<<5));
|
||||
regenMemmap(rom_remap);
|
||||
}
|
||||
|
||||
void viaCbPortBWrite(unsigned int val) {
|
||||
printf("VIA PORTB WRITE %x\n", val);
|
||||
int b;
|
||||
b=rtcCom(val&4, val&1, val&2);
|
||||
if (b) viaSet(VIA_PORTB, 1); else viaClear(VIA_PORTB, 1);
|
||||
|
@ -73,7 +73,8 @@
|
||||
* and m68k_read_pcrelative_xx() for PC-relative addressing.
|
||||
* If off, all read requests from the CPU will be redirected to m68k_read_xx()
|
||||
*/
|
||||
#define M68K_SEPARATE_READS OPT_OFF
|
||||
#define M68K_SEPARATE_READS OPT_ON
|
||||
|
||||
|
||||
/* If ON, the CPU will call m68k_write_32_pd() when it executes move.l with a
|
||||
* predecrement destination EA mode instead of m68k_write_32().
|
||||
@ -124,9 +125,9 @@
|
||||
* large value. This allows host programs to be nicer when it comes to
|
||||
* fetching immediate data and instructions on a banked memory system.
|
||||
*/
|
||||
#define M68K_MONITOR_PC OPT_OFF
|
||||
#define M68K_SET_PC_CALLBACK(A) your_pc_changed_handler_function(A)
|
||||
|
||||
#define M68K_MONITOR_PC OPT_SPECIFY_HANDLER
|
||||
#define M68K_SET_PC_CALLBACK(A) m68k_pc_changed_handler_function(A)
|
||||
void m68k_pc_changed_handler_function(unsigned int addr);
|
||||
|
||||
/* If ON, CPU will call the instruction hook callback before every
|
||||
* instruction.
|
||||
@ -185,4 +186,41 @@ void m68k_instruction();
|
||||
/* ============================== END OF FILE ============================= */
|
||||
/* ======================================================================== */
|
||||
|
||||
|
||||
#include <byteswap.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern unsigned char *m68k_pcbase;
|
||||
|
||||
static inline unsigned int m68k_read_immediate_16(unsigned int address) {
|
||||
address&=0xFFFFFF;
|
||||
uint16_t *p=(uint16_t*)(m68k_pcbase+address);
|
||||
return __bswap_16(*p);
|
||||
}
|
||||
|
||||
static inline unsigned int m68k_read_immediate_32(unsigned int address) {
|
||||
address&=0xFFFFFF;
|
||||
uint32_t *p=(uint32_t*)(m68k_pcbase+address);
|
||||
return __bswap_32(*p);
|
||||
}
|
||||
|
||||
static inline unsigned int m68k_read_pcrelative_8(unsigned int address) {
|
||||
address&=0xFFFFFF;
|
||||
uint8_t *p=(uint8_t*)(m68k_pcbase+address);
|
||||
return *p;
|
||||
}
|
||||
|
||||
static inline unsigned int m68k_read_pcrelative_16(unsigned int address) {
|
||||
address&=0xFFFFFF;
|
||||
uint16_t *p=(uint16_t*)(m68k_pcbase+address);
|
||||
return __bswap_16(*p);
|
||||
}
|
||||
|
||||
static inline unsigned int m68k_read_pcrelative_32(unsigned int address) {
|
||||
address&=0xFFFFFF;
|
||||
uint32_t *p=(uint32_t*)(m68k_pcbase+address);
|
||||
return __bswap_32(*p);
|
||||
}
|
||||
|
||||
|
||||
#endif /* M68KCONF__HEADER */
|
||||
|
@ -161,6 +161,7 @@ unsigned int m68k_read_memory_8(unsigned int address);
|
||||
unsigned int m68k_read_memory_16(unsigned int address);
|
||||
unsigned int m68k_read_memory_32(unsigned int address);
|
||||
|
||||
#if 0
|
||||
/* Read data immediately following the PC */
|
||||
unsigned int m68k_read_immediate_16(unsigned int address);
|
||||
unsigned int m68k_read_immediate_32(unsigned int address);
|
||||
@ -169,6 +170,7 @@ unsigned int m68k_read_immediate_32(unsigned int address);
|
||||
unsigned int m68k_read_pcrelative_8(unsigned int address);
|
||||
unsigned int m68k_read_pcrelative_16(unsigned int address);
|
||||
unsigned int m68k_read_pcrelative_32(unsigned int address);
|
||||
#endif
|
||||
|
||||
/* Memory access for the disassembler */
|
||||
unsigned int m68k_read_disassembler_8 (unsigned int address);
|
||||
|
@ -39,7 +39,7 @@ int rtcCom(int en, int dat, int clk) {
|
||||
} else if (rtc.pos==15) {
|
||||
if ((rtc.cmd&0x8000)==0) {
|
||||
rtc.mem[(rtc.cmd&0x7C00)>>10]=rtc.cmd&0xff;
|
||||
// saveRtcMem(rtc.mem);
|
||||
saveRtcMem(rtc.mem);
|
||||
}
|
||||
printf("RTC/PRAM CMD %x\n", rtc.cmd);
|
||||
}
|
||||
|
@ -111,10 +111,11 @@ static int rxBytesLeft(int chan) {
|
||||
return scc.chan[chan].rx[scc.chan[chan].rxBufCur].len-scc.chan[chan].rxPos;
|
||||
}
|
||||
|
||||
static int rxBufTick(int chan) {
|
||||
static int rxBufTick(int chan, int noTicks) {
|
||||
if (scc.chan[chan].rx[scc.chan[chan].rxBufCur].delay > 0) {
|
||||
scc.chan[chan].rx[scc.chan[chan].rxBufCur].delay--;
|
||||
if (scc.chan[chan].rx[scc.chan[chan].rxBufCur].delay==0) {
|
||||
scc.chan[chan].rx[scc.chan[chan].rxBufCur].delay-=noTicks;
|
||||
if (scc.chan[chan].rx[scc.chan[chan].rxBufCur].delay<=0) {
|
||||
scc.chan[chan].rx[scc.chan[chan].rxBufCur].delay=0;
|
||||
#ifdef SCC_DBG
|
||||
printf("Feeding buffer %d into SCC\n", scc.chan[chan].rxBufCur);
|
||||
#endif
|
||||
@ -592,30 +593,40 @@ unsigned int sccRead(unsigned int addr) {
|
||||
}
|
||||
|
||||
//Called at about 800KHz
|
||||
void sccTick() {
|
||||
void sccTick(int noTicks) {
|
||||
for (int n=0; n<2; n++) {
|
||||
int needCheck=0;
|
||||
if (scc.chan[n].txTimer>0) {
|
||||
scc.chan[n].txTimer--;
|
||||
if (scc.chan[n].txTimer==0) {
|
||||
scc.chan[n].txTimer-=noTicks;
|
||||
if (scc.chan[n].txTimer<=0) {
|
||||
scc.chan[n].txTimer=0;
|
||||
// printf("Tx buffer empty: Sent data\n");
|
||||
sccTxFinished(n);
|
||||
needCheck=1;
|
||||
}
|
||||
}
|
||||
if (rxBufTick(n)) {
|
||||
if (rxBufTick(n, noTicks)) {
|
||||
triggerRx(n);
|
||||
needCheck=1;
|
||||
}
|
||||
if (scc.chan[n].eofDelay>0) {
|
||||
scc.chan[n].eofDelay--;
|
||||
scc.chan[n].eofDelay-=noTicks;
|
||||
if (scc.chan[n].eofDelay<0) scc.chan[n].eofDelay=0;
|
||||
if (scc.chan[n].eofDelay==0 && (scc.chan[n].wr1&0x10)!=0) {
|
||||
//Int mode is recv char or special / special only
|
||||
printf("Raise EOF int for channel %d\n", n);
|
||||
scc.chan[n].eofIntPending=1;
|
||||
scc.intpending|=((n==0)?SCC_RR3_CHA_RX:SCC_RR3_CHB_RX);
|
||||
raiseInt(n);
|
||||
needCheck=1;
|
||||
}
|
||||
}
|
||||
if (scc.chan[n].rxAbrtTimer>0) scc.chan[n].rxAbrtTimer--;
|
||||
checkExtInt(n);
|
||||
if (scc.chan[n].rxAbrtTimer>0) {
|
||||
scc.chan[n].rxAbrtTimer-=noTicks;
|
||||
if (scc.chan[n].rxAbrtTimer<0) scc.chan[n].rxAbrtTimer=0;
|
||||
needCheck=1;
|
||||
}
|
||||
if (needCheck) checkExtInt(n);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,6 @@ void sccWrite(unsigned int addr, unsigned int val);
|
||||
unsigned int sccRead(unsigned int addr);
|
||||
void sccSetDcd(int chan, int val);
|
||||
void sccInit();
|
||||
void sccTick();
|
||||
void sccTick(int cycles);
|
||||
void sccRecv(int chan, uint8_t *data, int len, int delay);
|
||||
|
||||
|
@ -17,13 +17,16 @@ void sdlDie() {
|
||||
|
||||
|
||||
void dispInit() {
|
||||
if (SDL_Init( SDL_INIT_VIDEO|SDL_INIT_AUDIO ) < 0 ) sdlDie();
|
||||
win=SDL_CreateWindow( "TME", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
|
||||
if (win==0) sdlDie();
|
||||
surf=SDL_GetWindowSurface(win);
|
||||
drwsurf=SDL_CreateRGBSurfaceWithFormat(0, SCREEN_WIDTH, SCREEN_HEIGHT, 32, SDL_PIXELFORMAT_RGBA32);
|
||||
}
|
||||
|
||||
void sdlDispAudioInit() {
|
||||
if (SDL_Init( SDL_INIT_VIDEO|SDL_INIT_AUDIO ) < 0 ) sdlDie();
|
||||
}
|
||||
|
||||
void handleInput() {
|
||||
static int btn=0;
|
||||
static int oldx, oldy;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <unistd.h>
|
||||
#include "tmeconfig.h"
|
||||
#include "snd.h"
|
||||
#include "disp.h"
|
||||
|
||||
static void *loadRom(char *file) {
|
||||
int i;
|
||||
@ -38,5 +39,6 @@ int main(int argc, char **argv) {
|
||||
rtcInit(data);
|
||||
fclose(f);
|
||||
}
|
||||
sdlDispAudioInit();
|
||||
tmeStartEmu(rom);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ static int bufLen() {
|
||||
}
|
||||
|
||||
int sndDone() {
|
||||
if (!soundEna) return 1;
|
||||
if (!soundEna) return 0;
|
||||
return (bufLen()<512);
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@
|
||||
#define TME_SCREENBUF 0x3FA700
|
||||
#define TME_SCREENBUF_ALT 0x3F2700
|
||||
#define TME_SNDBUF 0x3FFD00
|
||||
#define TME_SNDBUF_ALT 0x3FA100
|
||||
|
||||
|
||||
#else
|
||||
@ -40,9 +39,9 @@
|
||||
#define TME_SCREENBUF 0x1A700
|
||||
#define TME_SCREENBUF_ALT 0x12700
|
||||
#define TME_SNDBUF 0x1FD00
|
||||
#define TME_SNDBUF_ALT 0x1A100
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
//Source: Guide to the Macintosh family hardware
|
||||
#define TME_SNDBUF_ALT (TME_SNDBUF-0x5C00)
|
||||
|
@ -30,7 +30,7 @@ typedef struct {
|
||||
uint8_t ddra, ddrb;
|
||||
uint8_t ina, inb;
|
||||
uint8_t outa, outb;
|
||||
uint16_t timer1, timer2;
|
||||
int timer1, timer2;
|
||||
uint16_t latch1, latch2;
|
||||
uint8_t ifr, ier;
|
||||
uint8_t pcr, acr;
|
||||
@ -54,18 +54,19 @@ void viaClear(int no, int mask) {
|
||||
}
|
||||
|
||||
void viaStep(int clockcycles) {
|
||||
while(clockcycles--) {
|
||||
if (via.timer1==1) {
|
||||
if ((via.timer1!=0) || (via.acr&(1<<6))) {
|
||||
via.timer1-=clockcycles;
|
||||
if (via.timer1<=1) {
|
||||
via.ifr|=IFR_T1;
|
||||
via.timer1=via.latch1;
|
||||
}
|
||||
if ((via.timer1!=0) || (via.acr&(1<<6))) via.timer1--;
|
||||
via.timer2--;
|
||||
if (via.timer2==0) {
|
||||
//Actually shouldn't be set when timer2 gets 0 a 2nd time... ahwell.
|
||||
via.ifr|=IFR_T2;
|
||||
via.timer1+=via.latch1;
|
||||
}
|
||||
}
|
||||
via.timer2-=clockcycles;
|
||||
if (via.timer2<=0) {
|
||||
//Actually shouldn't be set when timer2 gets 0 a 2nd time... ahwell.
|
||||
via.ifr|=IFR_T2;
|
||||
via.timer2+=0x10000;
|
||||
}
|
||||
}
|
||||
|
||||
static void viaCheckIrq() {
|
||||
@ -119,10 +120,12 @@ void viaWrite(unsigned int addr, unsigned int val) {
|
||||
if (addr==0x0) {
|
||||
//ORB
|
||||
viaCbPortBWrite(val);
|
||||
via.inb=(via.inb&~via.ddrb)|(val&via.ddrb);
|
||||
accessPort(1);
|
||||
} else if (addr==0x1) {
|
||||
//ORA
|
||||
viaCbPortAWrite(val);
|
||||
via.ina=(via.ina&~via.ddra)|(val&via.ddra);
|
||||
accessPort(0);
|
||||
} else if (addr==0x2) {
|
||||
//DDRB
|
||||
@ -180,6 +183,7 @@ void viaWrite(unsigned int addr, unsigned int val) {
|
||||
} else if (addr==0xf) {
|
||||
//ORA
|
||||
viaCbPortAWrite(val);
|
||||
via.ina=(via.ina&~via.ddra)|(val&via.ddra);
|
||||
}
|
||||
// printf("VIA write %s val %x\n", viaRegNames[addr], val);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user