mirror of
https://github.com/libretro/Play-.git
synced 2025-02-03 15:33:45 +00:00
git-svn-id: http://svn.purei.org/purei/trunk@334 b36208d7-6611-0410-8bec-b1987f11c4a2
This commit is contained in:
parent
6d6b93bb5d
commit
e12a8893a0
@ -182,172 +182,172 @@ void CPsxBios::HandleInterrupt()
|
||||
void CPsxBios::HandleException()
|
||||
{
|
||||
assert(m_cpu.m_State.nHasException);
|
||||
// uint32 searchAddress = m_cpu.m_State.nCOP0[CCOP_SCU::EPC];
|
||||
uint32 searchAddress = m_cpu.m_State.nGPR[CMIPS::K1].nV0;
|
||||
uint32 callInstruction = m_cpu.m_pMemoryMap->GetWord(searchAddress);
|
||||
if(callInstruction != 0x0000000C)
|
||||
{
|
||||
throw runtime_error("Not a SYSCALL.");
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
DisassembleSyscall(searchAddress);
|
||||
#endif
|
||||
if(searchAddress == 0xA0)
|
||||
{
|
||||
ProcessSubFunction(m_handlerA0, MAX_HANDLER_A0);
|
||||
}
|
||||
else if(searchAddress == 0xB0)
|
||||
{
|
||||
ProcessSubFunction(m_handlerB0, MAX_HANDLER_B0);
|
||||
}
|
||||
else if(searchAddress == 0xC0)
|
||||
{
|
||||
ProcessSubFunction(m_handlerC0, MAX_HANDLER_C0);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::A0].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x01:
|
||||
sc_EnterCriticalSection();
|
||||
break;
|
||||
case 0x02:
|
||||
sc_ExitCriticalSection();
|
||||
break;
|
||||
default:
|
||||
sc_Illegal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// uint32 searchAddress = m_cpu.m_State.nCOP0[CCOP_SCU::EPC];
|
||||
uint32 searchAddress = m_cpu.m_State.nGPR[CMIPS::K1].nV0;
|
||||
uint32 callInstruction = m_cpu.m_pMemoryMap->GetWord(searchAddress);
|
||||
if(callInstruction != 0x0000000C)
|
||||
{
|
||||
throw runtime_error("Not a SYSCALL.");
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
DisassembleSyscall(searchAddress);
|
||||
#endif
|
||||
if(searchAddress == 0xA0)
|
||||
{
|
||||
ProcessSubFunction(m_handlerA0, MAX_HANDLER_A0);
|
||||
}
|
||||
else if(searchAddress == 0xB0)
|
||||
{
|
||||
ProcessSubFunction(m_handlerB0, MAX_HANDLER_B0);
|
||||
}
|
||||
else if(searchAddress == 0xC0)
|
||||
{
|
||||
ProcessSubFunction(m_handlerC0, MAX_HANDLER_C0);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::A0].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x01:
|
||||
sc_EnterCriticalSection();
|
||||
break;
|
||||
case 0x02:
|
||||
sc_ExitCriticalSection();
|
||||
break;
|
||||
default:
|
||||
sc_Illegal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_cpu.m_State.nHasException = 0;
|
||||
}
|
||||
|
||||
void CPsxBios::ProcessSubFunction(SyscallHandler* handlerTable, unsigned int handlerTableLength)
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::T1].nV0;
|
||||
if(functionId >= handlerTableLength)
|
||||
{
|
||||
sc_Illegal();
|
||||
}
|
||||
functionId %= handlerTableLength;
|
||||
((this)->*(handlerTable[functionId]))();
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::T1].nV0;
|
||||
if(functionId >= handlerTableLength)
|
||||
{
|
||||
sc_Illegal();
|
||||
}
|
||||
functionId %= handlerTableLength;
|
||||
((this)->*(handlerTable[functionId]))();
|
||||
}
|
||||
|
||||
void CPsxBios::DisassembleSyscall(uint32 searchAddress)
|
||||
{
|
||||
if(searchAddress == 0x00A0)
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::T1].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x39:
|
||||
CLog::GetInstance().Print(LOG_NAME, "InitHeap(block = 0x%0.8X, n = 0x%0.8X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0);
|
||||
break;
|
||||
case 0x70:
|
||||
CLog::GetInstance().Print(LOG_NAME, "_bu_init();\r\n");
|
||||
break;
|
||||
case 0x72:
|
||||
CLog::GetInstance().Print(LOG_NAME, "_96_remove();\r\n");
|
||||
break;
|
||||
case 0x9F:
|
||||
CLog::GetInstance().Print(LOG_NAME, "SetMem(size = %i);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
default:
|
||||
CLog::GetInstance().Print(LOG_NAME, "Unknown system call encountered (0xA0, 0x%X).\r\n", functionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(searchAddress == 0x00B0)
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::T1].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x00:
|
||||
CLog::GetInstance().Print(LOG_NAME, "SysMalloc(size = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x07:
|
||||
CLog::GetInstance().Print(LOG_NAME, "DeliverEvent(class = 0x%X, event = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0);
|
||||
break;
|
||||
case 0x08:
|
||||
CLog::GetInstance().Print(LOG_NAME, "OpenEvent(class = 0x%X, spec = 0x%X, mode = 0x%X, func = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM2].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM3].nV0);
|
||||
break;
|
||||
case 0x0A:
|
||||
CLog::GetInstance().Print(LOG_NAME, "WaitEvent(event = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x0C:
|
||||
CLog::GetInstance().Print(LOG_NAME, "EnableEvent(event = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x0D:
|
||||
CLog::GetInstance().Print(LOG_NAME, "DisableEvent(event = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x17:
|
||||
CLog::GetInstance().Print(LOG_NAME, "ReturnFromException();\r\n");
|
||||
break;
|
||||
case 0x19:
|
||||
CLog::GetInstance().Print(LOG_NAME, "HookEntryInt(address = 0x%0.8X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x5B:
|
||||
CLog::GetInstance().Print(LOG_NAME, "ChangeClearPad(param = %i);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
default:
|
||||
CLog::GetInstance().Print(LOG_NAME, "Unknown system call encountered (0xB0, 0x%X).\r\n", functionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(searchAddress == 0x00C0)
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::T1].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x03:
|
||||
CLog::GetInstance().Print(LOG_NAME, "SysDeqIntRP(index = %i, queue = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0);
|
||||
break;
|
||||
case 0x08:
|
||||
CLog::GetInstance().Print(LOG_NAME, "SysInitMemory();\r\n");
|
||||
break;
|
||||
case 0x0A:
|
||||
CLog::GetInstance().Print(LOG_NAME, "ChangeClearRCnt(param0 = %i, param1 = %i);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0);
|
||||
break;
|
||||
default:
|
||||
CLog::GetInstance().Print(LOG_NAME, "Unknown system call encountered (0xC0, 0x%X).\r\n", functionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::A0].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x01:
|
||||
CLog::GetInstance().Print(LOG_NAME, "EnterCriticalSection();\r\n");
|
||||
break;
|
||||
case 0x02:
|
||||
CLog::GetInstance().Print(LOG_NAME, "ExitCriticalSection();\r\n");
|
||||
break;
|
||||
default:
|
||||
CLog::GetInstance().Print(LOG_NAME, "Unknown system call encountered.\r\n");
|
||||
break;
|
||||
}
|
||||
if(searchAddress == 0x00A0)
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::T1].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x39:
|
||||
CLog::GetInstance().Print(LOG_NAME, "InitHeap(block = 0x%0.8X, n = 0x%0.8X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0);
|
||||
break;
|
||||
case 0x70:
|
||||
CLog::GetInstance().Print(LOG_NAME, "_bu_init();\r\n");
|
||||
break;
|
||||
case 0x72:
|
||||
CLog::GetInstance().Print(LOG_NAME, "_96_remove();\r\n");
|
||||
break;
|
||||
case 0x9F:
|
||||
CLog::GetInstance().Print(LOG_NAME, "SetMem(size = %i);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
default:
|
||||
CLog::GetInstance().Print(LOG_NAME, "Unknown system call encountered (0xA0, 0x%X).\r\n", functionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(searchAddress == 0x00B0)
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::T1].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x00:
|
||||
CLog::GetInstance().Print(LOG_NAME, "SysMalloc(size = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x07:
|
||||
CLog::GetInstance().Print(LOG_NAME, "DeliverEvent(class = 0x%X, event = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0);
|
||||
break;
|
||||
case 0x08:
|
||||
CLog::GetInstance().Print(LOG_NAME, "OpenEvent(class = 0x%X, spec = 0x%X, mode = 0x%X, func = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM2].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM3].nV0);
|
||||
break;
|
||||
case 0x0A:
|
||||
CLog::GetInstance().Print(LOG_NAME, "WaitEvent(event = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x0C:
|
||||
CLog::GetInstance().Print(LOG_NAME, "EnableEvent(event = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x0D:
|
||||
CLog::GetInstance().Print(LOG_NAME, "DisableEvent(event = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x17:
|
||||
CLog::GetInstance().Print(LOG_NAME, "ReturnFromException();\r\n");
|
||||
break;
|
||||
case 0x19:
|
||||
CLog::GetInstance().Print(LOG_NAME, "HookEntryInt(address = 0x%0.8X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
case 0x5B:
|
||||
CLog::GetInstance().Print(LOG_NAME, "ChangeClearPad(param = %i);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0);
|
||||
break;
|
||||
default:
|
||||
CLog::GetInstance().Print(LOG_NAME, "Unknown system call encountered (0xB0, 0x%X).\r\n", functionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(searchAddress == 0x00C0)
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::T1].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x03:
|
||||
CLog::GetInstance().Print(LOG_NAME, "SysDeqIntRP(index = %i, queue = 0x%X);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0);
|
||||
break;
|
||||
case 0x08:
|
||||
CLog::GetInstance().Print(LOG_NAME, "SysInitMemory();\r\n");
|
||||
break;
|
||||
case 0x0A:
|
||||
CLog::GetInstance().Print(LOG_NAME, "ChangeClearRCnt(param0 = %i, param1 = %i);\r\n",
|
||||
m_cpu.m_State.nGPR[SC_PARAM0].nV0,
|
||||
m_cpu.m_State.nGPR[SC_PARAM1].nV0);
|
||||
break;
|
||||
default:
|
||||
CLog::GetInstance().Print(LOG_NAME, "Unknown system call encountered (0xC0, 0x%X).\r\n", functionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 functionId = m_cpu.m_State.nGPR[CMIPS::A0].nV0;
|
||||
switch(functionId)
|
||||
{
|
||||
case 0x01:
|
||||
CLog::GetInstance().Print(LOG_NAME, "EnterCriticalSection();\r\n");
|
||||
break;
|
||||
case 0x02:
|
||||
CLog::GetInstance().Print(LOG_NAME, "ExitCriticalSection();\r\n");
|
||||
break;
|
||||
default:
|
||||
CLog::GetInstance().Print(LOG_NAME, "Unknown system call encountered.\r\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,9 +52,13 @@ CPsxVm::~CPsxVm()
|
||||
void CPsxVm::Reset()
|
||||
{
|
||||
memset(m_ram, 0, RAMSIZE);
|
||||
m_executor.Clear();
|
||||
m_cpu.Reset();
|
||||
m_bios.Reset();
|
||||
m_spu.Reset();
|
||||
m_counters.Reset();
|
||||
m_dmac.Reset();
|
||||
m_intc.Reset();
|
||||
}
|
||||
|
||||
uint32 CPsxVm::ReadIoRegister(uint32 address)
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "SH_OpenAL.h"
|
||||
#include "alloca_def.h"
|
||||
|
||||
#define LOGGING
|
||||
//#define LOGGING
|
||||
|
||||
ALCint g_attrList[] =
|
||||
{
|
||||
@ -45,7 +45,7 @@ void CSH_OpenAL::Update(CSpu& spu)
|
||||
|
||||
//Update bufferLength worth of samples
|
||||
|
||||
const unsigned int sampleCount = (44100 * bufferLength) / 1000;
|
||||
const unsigned int sampleCount = (44100 * bufferLength * 2) / 1000;
|
||||
const unsigned int sampleRate = 44100;
|
||||
int16 samples[sampleCount];
|
||||
spu.Render(samples, sampleCount, sampleRate);
|
||||
@ -55,7 +55,7 @@ void CSH_OpenAL::Update(CSpu& spu)
|
||||
ALuint buffer = *m_availableBuffers.begin();
|
||||
m_availableBuffers.pop_front();
|
||||
|
||||
alBufferData(buffer, AL_FORMAT_MONO16, samples, sampleCount * sizeof(int16), sampleRate);
|
||||
alBufferData(buffer, AL_FORMAT_STEREO16, samples, sampleCount * sizeof(int16), sampleRate);
|
||||
#ifdef LOGGING
|
||||
FILE* log = fopen("log.raw", "ab");
|
||||
fwrite(samples, sampleCount * sizeof(int16), 1, log);
|
||||
|
@ -120,8 +120,10 @@ CSpu::CHANNEL& CSpu::GetChannel(unsigned int channelNumber)
|
||||
|
||||
void CSpu::Render(int16* samples, unsigned int sampleCount, unsigned int sampleRate)
|
||||
{
|
||||
assert((sampleCount & 0x01) == 0);
|
||||
unsigned int ticks = sampleCount / 2;
|
||||
memset(samples, 0, sizeof(int16) * sampleCount);
|
||||
int16* bufferTemp = reinterpret_cast<int16*>(_alloca(sizeof(int16) * sampleCount));
|
||||
int16* bufferTemp = reinterpret_cast<int16*>(_alloca(sizeof(int16) * ticks));
|
||||
for(unsigned int i = 0; i < 24; i++)
|
||||
// for(unsigned int i = 1; i < 2; i++)
|
||||
{
|
||||
@ -135,19 +137,33 @@ void CSpu::Render(int16* samples, unsigned int sampleCount, unsigned int sampleR
|
||||
channel.status = ATTACK;
|
||||
}
|
||||
reader.SetPitch(channel.pitch);
|
||||
reader.GetSamples(bufferTemp, sampleCount, sampleRate);
|
||||
reader.GetSamples(bufferTemp, ticks, sampleRate);
|
||||
//Mix samples
|
||||
for(unsigned int j = 0; j < sampleCount; j++)
|
||||
int16* samplePtr = samples;
|
||||
for(unsigned int j = 0; j < ticks; j++)
|
||||
{
|
||||
if(channel.status == STOPPED) break;
|
||||
if(channel.status == RELEASE)
|
||||
{
|
||||
channel.status = STOPPED;
|
||||
}
|
||||
int32 resultSample = static_cast<int32>(bufferTemp[j]) + static_cast<int32>(samples[j]);
|
||||
resultSample = max<int32>(resultSample, SHRT_MIN);
|
||||
resultSample = min<int32>(resultSample, SHRT_MAX);
|
||||
samples[j] = static_cast<int16>(resultSample);
|
||||
int32 inputSample = static_cast<int32>(bufferTemp[j]);
|
||||
struct SampleMixer
|
||||
{
|
||||
void operator() (int32 inputSample, const CHANNEL_VOLUME& volume, int16*& output) const
|
||||
{
|
||||
if(!volume.mode.mode)
|
||||
{
|
||||
inputSample = (inputSample * static_cast<int32>(volume.volume.volume)) / 0x3FFF;
|
||||
}
|
||||
int32 resultSample = inputSample + static_cast<int32>(*output);
|
||||
resultSample = max<int32>(resultSample, SHRT_MIN);
|
||||
resultSample = min<int32>(resultSample, SHRT_MAX);
|
||||
(*output++) = static_cast<int16>(resultSample);
|
||||
}
|
||||
};
|
||||
SampleMixer()(inputSample, channel.volumeLeft, samplePtr);
|
||||
SampleMixer()(inputSample, channel.volumeRight, samplePtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -234,10 +250,10 @@ void CSpu::WriteRegister(uint32 address, uint16 value)
|
||||
switch(registerId)
|
||||
{
|
||||
case CH_VOL_LEFT:
|
||||
m_channel[channel].volumeLeft = value;
|
||||
m_channel[channel].volumeLeft <<= value;
|
||||
break;
|
||||
case CH_VOL_RIGHT:
|
||||
m_channel[channel].volumeRight = value;
|
||||
m_channel[channel].volumeRight <<= value;
|
||||
break;
|
||||
case CH_PITCH:
|
||||
m_channel[channel].pitch = value;
|
||||
|
@ -28,17 +28,45 @@ public:
|
||||
};
|
||||
BOOST_STATIC_ASSERT(sizeof(ADSR_RATE) >= sizeof(uint16));
|
||||
|
||||
struct CHANNEL_VOLUME : public convertible<uint16>
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
unsigned int unused0 : 15;
|
||||
unsigned int mode : 1;
|
||||
} mode;
|
||||
struct
|
||||
{
|
||||
unsigned int volume : 14;
|
||||
unsigned int phase : 1;
|
||||
unsigned int mode : 1;
|
||||
} volume;
|
||||
struct
|
||||
{
|
||||
unsigned int volume : 7;
|
||||
unsigned int unused0 : 5;
|
||||
unsigned int phase : 1;
|
||||
unsigned int decrease : 1;
|
||||
unsigned int slope : 1;
|
||||
unsigned int mode : 1;
|
||||
} sweep;
|
||||
};
|
||||
};
|
||||
BOOST_STATIC_ASSERT(sizeof(CHANNEL_VOLUME) >= sizeof(uint16));
|
||||
|
||||
struct CHANNEL
|
||||
{
|
||||
uint16 volumeLeft;
|
||||
uint16 volumeRight;
|
||||
uint16 pitch;
|
||||
uint16 address;
|
||||
ADSR_LEVEL adsrLevel;
|
||||
ADSR_RATE adsrRate;
|
||||
int32 adsrVolume;
|
||||
uint16 repeat;
|
||||
uint16 status;
|
||||
CHANNEL_VOLUME volumeLeft;
|
||||
CHANNEL_VOLUME volumeRight;
|
||||
uint16 pitch;
|
||||
uint16 address;
|
||||
ADSR_LEVEL adsrLevel;
|
||||
ADSR_RATE adsrRate;
|
||||
int32 adsrVolume;
|
||||
uint16 repeat;
|
||||
uint16 status;
|
||||
};
|
||||
|
||||
CSpu();
|
||||
|
Loading…
x
Reference in New Issue
Block a user