mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 02:32:36 +01:00
Dynamic XAudio version detection
This commit is contained in:
parent
0ae8cc4467
commit
a0c0df6e9d
1158
3rdparty/minidx12/Include/xaudio2.h
vendored
Normal file
1158
3rdparty/minidx12/Include/xaudio2.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
149
rpcs3/Emu/Audio/XAudio2/XAudio27Thread.cpp
Normal file
149
rpcs3/Emu/Audio/XAudio2/XAudio27Thread.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "Utilities/Log.h"
|
||||
#include "Utilities/StrFmt.h"
|
||||
#include "Utilities/Config.h"
|
||||
#include "Emu/System.h"
|
||||
|
||||
#include "XAudio2Thread.h"
|
||||
#include "3rdparty/XAudio2_7/XAudio2.h"
|
||||
|
||||
extern cfg::bool_entry g_cfg_audio_convert_to_u16;
|
||||
|
||||
static thread_local IXAudio2* s_tls_xaudio2_instance{};
|
||||
static thread_local IXAudio2MasteringVoice* s_tls_master_voice{};
|
||||
static thread_local IXAudio2SourceVoice* s_tls_source_voice{};
|
||||
|
||||
void XAudio2Thread::xa27_init()
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : CoInitializeEx() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
hr = XAudio2Create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : XAudio2Create() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
hr = s_tls_xaudio2_instance->CreateMasteringVoice(&s_tls_master_voice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : CreateMasteringVoice() failed(0x%08x)", (u32)hr);
|
||||
s_tls_xaudio2_instance->Release();
|
||||
Emu.Pause();
|
||||
}
|
||||
|
||||
LOG_SUCCESS(GENERAL, "XAudio 2.7 initialized");
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa27_destroy()
|
||||
{
|
||||
if (s_tls_source_voice != nullptr)
|
||||
{
|
||||
s_tls_source_voice->Stop();
|
||||
s_tls_source_voice->DestroyVoice();
|
||||
}
|
||||
|
||||
if (s_tls_master_voice != nullptr)
|
||||
{
|
||||
s_tls_master_voice->DestroyVoice();
|
||||
}
|
||||
|
||||
if (s_tls_xaudio2_instance != nullptr)
|
||||
{
|
||||
s_tls_xaudio2_instance->StopEngine();
|
||||
s_tls_xaudio2_instance->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa27_play()
|
||||
{
|
||||
HRESULT hr = s_tls_source_voice->Start();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : Start() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa27_flush()
|
||||
{
|
||||
HRESULT hr = s_tls_source_voice->FlushSourceBuffers();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : FlushSourceBuffers() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa27_stop()
|
||||
{
|
||||
HRESULT hr = s_tls_source_voice->Stop();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : Stop() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa27_open()
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
WORD sample_size = g_cfg_audio_convert_to_u16 ? sizeof(u16) : sizeof(float);
|
||||
WORD channels = 8;
|
||||
|
||||
WAVEFORMATEX waveformatex;
|
||||
waveformatex.wFormatTag = g_cfg_audio_convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
|
||||
waveformatex.nChannels = channels;
|
||||
waveformatex.nSamplesPerSec = 48000;
|
||||
waveformatex.nAvgBytesPerSec = 48000 * (DWORD)channels * (DWORD)sample_size;
|
||||
waveformatex.nBlockAlign = channels * sample_size;
|
||||
waveformatex.wBitsPerSample = sample_size * 8;
|
||||
waveformatex.cbSize = 0;
|
||||
|
||||
hr = s_tls_xaudio2_instance->CreateSourceVoice(&s_tls_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : CreateSourceVoice() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
s_tls_source_voice->SetVolume(4.0);
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa27_add(const void* src, int size)
|
||||
{
|
||||
XAUDIO2_BUFFER buffer;
|
||||
|
||||
buffer.AudioBytes = size;
|
||||
buffer.Flags = 0;
|
||||
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
|
||||
buffer.LoopCount = 0;
|
||||
buffer.LoopLength = 0;
|
||||
buffer.pAudioData = (const BYTE*)src;
|
||||
buffer.pContext = 0;
|
||||
buffer.PlayBegin = 0;
|
||||
buffer.PlayLength = 256;
|
||||
|
||||
HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : AddData() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
141
rpcs3/Emu/Audio/XAudio2/XAudio28Thread.cpp
Normal file
141
rpcs3/Emu/Audio/XAudio2/XAudio28Thread.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "Utilities/Log.h"
|
||||
#include "Utilities/StrFmt.h"
|
||||
#include "Utilities/Config.h"
|
||||
#include "Emu/System.h"
|
||||
|
||||
#include "XAudio2Thread.h"
|
||||
#include "3rdparty/minidx12/Include/xaudio2.h"
|
||||
|
||||
extern cfg::bool_entry g_cfg_audio_convert_to_u16;
|
||||
|
||||
static thread_local IXAudio2* s_tls_xaudio2_instance{};
|
||||
static thread_local IXAudio2MasteringVoice* s_tls_master_voice{};
|
||||
static thread_local IXAudio2SourceVoice* s_tls_source_voice{};
|
||||
|
||||
void XAudio2Thread::xa28_init(void* module)
|
||||
{
|
||||
auto create = (XAudio2Create)GetProcAddress((HMODULE)module, "XAudio2Create");
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
hr = create(&s_tls_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : XAudio2Create() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
hr = s_tls_xaudio2_instance->CreateMasteringVoice(&s_tls_master_voice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : CreateMasteringVoice() failed(0x%08x)", (u32)hr);
|
||||
s_tls_xaudio2_instance->Release();
|
||||
Emu.Pause();
|
||||
}
|
||||
|
||||
LOG_SUCCESS(GENERAL, "XAudio 2.8 initialized");
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa28_destroy()
|
||||
{
|
||||
if (s_tls_source_voice != nullptr)
|
||||
{
|
||||
s_tls_source_voice->Stop();
|
||||
s_tls_source_voice->DestroyVoice();
|
||||
}
|
||||
|
||||
if (s_tls_master_voice != nullptr)
|
||||
{
|
||||
s_tls_master_voice->DestroyVoice();
|
||||
}
|
||||
|
||||
if (s_tls_xaudio2_instance != nullptr)
|
||||
{
|
||||
s_tls_xaudio2_instance->StopEngine();
|
||||
s_tls_xaudio2_instance->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa28_play()
|
||||
{
|
||||
HRESULT hr = s_tls_source_voice->Start();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : Start() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa28_flush()
|
||||
{
|
||||
HRESULT hr = s_tls_source_voice->FlushSourceBuffers();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : FlushSourceBuffers() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa28_stop()
|
||||
{
|
||||
HRESULT hr = s_tls_source_voice->Stop();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : Stop() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa28_open()
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
WORD sample_size = g_cfg_audio_convert_to_u16 ? sizeof(u16) : sizeof(float);
|
||||
WORD channels = 8;
|
||||
|
||||
WAVEFORMATEX waveformatex;
|
||||
waveformatex.wFormatTag = g_cfg_audio_convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
|
||||
waveformatex.nChannels = channels;
|
||||
waveformatex.nSamplesPerSec = 48000;
|
||||
waveformatex.nAvgBytesPerSec = 48000 * (DWORD)channels * (DWORD)sample_size;
|
||||
waveformatex.nBlockAlign = channels * sample_size;
|
||||
waveformatex.wBitsPerSample = sample_size * 8;
|
||||
waveformatex.cbSize = 0;
|
||||
|
||||
hr = s_tls_xaudio2_instance->CreateSourceVoice(&s_tls_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : CreateSourceVoice() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
s_tls_source_voice->SetVolume(4.0);
|
||||
}
|
||||
|
||||
void XAudio2Thread::xa28_add(const void* src, int size)
|
||||
{
|
||||
XAUDIO2_BUFFER buffer;
|
||||
|
||||
buffer.AudioBytes = size;
|
||||
buffer.Flags = 0;
|
||||
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
|
||||
buffer.LoopCount = 0;
|
||||
buffer.LoopLength = 0;
|
||||
buffer.pAudioData = (const BYTE*)src;
|
||||
buffer.pContext = 0;
|
||||
buffer.PlayBegin = 0;
|
||||
buffer.PlayLength = 256;
|
||||
|
||||
HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : AddData() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,148 +1,54 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "Utilities/Log.h"
|
||||
#include "Utilities/StrFmt.h"
|
||||
#include "Utilities/Config.h"
|
||||
#include "Emu/System.h"
|
||||
|
||||
#include "XAudio2Thread.h"
|
||||
#include <Windows.h>
|
||||
|
||||
extern cfg::bool_entry g_cfg_audio_convert_to_u16;
|
||||
|
||||
XAudio2Thread::XAudio2Thread()
|
||||
: m_xaudio2_instance(nullptr)
|
||||
, m_master_voice(nullptr)
|
||||
, m_source_voice(nullptr)
|
||||
: m_xaudio(LoadLibraryA("xaudio2_8.dll"))
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : CoInitializeEx() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
hr = XAudio2Create(&m_xaudio2_instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : XAudio2Create() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
hr = m_xaudio2_instance->CreateMasteringVoice(&m_master_voice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : CreateMasteringVoice() failed(0x%08x)", (u32)hr);
|
||||
m_xaudio2_instance->Release();
|
||||
Emu.Pause();
|
||||
}
|
||||
m_xaudio ? xa28_init(m_xaudio) : xa27_init();
|
||||
}
|
||||
|
||||
XAudio2Thread::~XAudio2Thread()
|
||||
{
|
||||
if (m_source_voice != nullptr)
|
||||
{
|
||||
m_source_voice->Stop();
|
||||
m_source_voice->DestroyVoice();
|
||||
}
|
||||
m_xaudio ? xa28_destroy() : xa27_destroy();
|
||||
|
||||
if (m_master_voice != nullptr)
|
||||
{
|
||||
m_master_voice->DestroyVoice();
|
||||
}
|
||||
|
||||
if (m_xaudio2_instance != nullptr)
|
||||
{
|
||||
m_xaudio2_instance->StopEngine();
|
||||
m_xaudio2_instance->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
FreeLibrary((HMODULE)m_xaudio);
|
||||
}
|
||||
|
||||
void XAudio2Thread::Play()
|
||||
{
|
||||
HRESULT hr = m_source_voice->Start();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : Start() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
m_xaudio ? xa28_play() : xa27_play();
|
||||
}
|
||||
|
||||
void XAudio2Thread::Close()
|
||||
{
|
||||
Stop();
|
||||
HRESULT hr = m_source_voice->FlushSourceBuffers();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : FlushSourceBuffers() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
m_xaudio ? xa28_flush() : xa27_flush();
|
||||
}
|
||||
|
||||
void XAudio2Thread::Stop()
|
||||
{
|
||||
HRESULT hr = m_source_voice->Stop();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : Stop() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
m_xaudio ? xa28_stop() : xa27_stop();
|
||||
}
|
||||
|
||||
void XAudio2Thread::Open(const void* src, int size)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
WORD sample_size = g_cfg_audio_convert_to_u16 ? sizeof(u16) : sizeof(float);
|
||||
WORD channels = 8;
|
||||
|
||||
WAVEFORMATEX waveformatex;
|
||||
waveformatex.wFormatTag = g_cfg_audio_convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
|
||||
waveformatex.nChannels = channels;
|
||||
waveformatex.nSamplesPerSec = 48000;
|
||||
waveformatex.nAvgBytesPerSec = 48000 * (DWORD)channels * (DWORD)sample_size;
|
||||
waveformatex.nBlockAlign = channels * sample_size;
|
||||
waveformatex.wBitsPerSample = sample_size * 8;
|
||||
waveformatex.cbSize = 0;
|
||||
|
||||
hr = m_xaudio2_instance->CreateSourceVoice(&m_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : CreateSourceVoice() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
m_source_voice->SetVolume(4.0);
|
||||
|
||||
m_xaudio ? xa28_open() : xa27_open();
|
||||
AddData(src, size);
|
||||
Play();
|
||||
}
|
||||
|
||||
void XAudio2Thread::AddData(const void* src, int size)
|
||||
{
|
||||
XAUDIO2_BUFFER buffer;
|
||||
|
||||
buffer.AudioBytes = size;
|
||||
buffer.Flags = 0;
|
||||
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;
|
||||
buffer.LoopCount = 0;
|
||||
buffer.LoopLength = 0;
|
||||
buffer.pAudioData = (const BYTE*)src;
|
||||
buffer.pContext = 0;
|
||||
buffer.PlayBegin = 0;
|
||||
buffer.PlayLength = 256;
|
||||
|
||||
HRESULT hr = m_source_voice->SubmitSourceBuffer(&buffer);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "XAudio2Thread : AddData() failed(0x%08x)", (u32)hr);
|
||||
Emu.Pause();
|
||||
}
|
||||
m_xaudio ? xa28_add(src, size) : xa27_add(src, size);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -4,13 +4,25 @@
|
||||
|
||||
#include "Emu/Audio/AudioThread.h"
|
||||
|
||||
#include "3rdparty/XAudio2_7/XAudio2.h"
|
||||
|
||||
class XAudio2Thread : public AudioThread
|
||||
{
|
||||
IXAudio2* m_xaudio2_instance;
|
||||
IXAudio2MasteringVoice* m_master_voice;
|
||||
IXAudio2SourceVoice* m_source_voice;
|
||||
void* const m_xaudio;
|
||||
|
||||
static void xa27_init();
|
||||
static void xa27_destroy();
|
||||
static void xa27_play();
|
||||
static void xa27_flush();
|
||||
static void xa27_stop();
|
||||
static void xa27_open();
|
||||
static void xa27_add(const void*, int);
|
||||
|
||||
static void xa28_init(void*);
|
||||
static void xa28_destroy();
|
||||
static void xa28_play();
|
||||
static void xa28_flush();
|
||||
static void xa28_stop();
|
||||
static void xa28_open();
|
||||
static void xa28_add(const void*, int);
|
||||
|
||||
public:
|
||||
XAudio2Thread();
|
||||
|
@ -76,6 +76,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\rpcs3\Emu\Audio\XAudio2\XAudio2Thread.cpp" />
|
||||
<ClCompile Include="Emu\Audio\XAudio2\XAudio27Thread.cpp" />
|
||||
<ClCompile Include="Emu\Audio\XAudio2\XAudio28Thread.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
@ -10,6 +10,12 @@
|
||||
<ClCompile Include="..\rpcs3\Emu\Audio\XAudio2\XAudio2Thread.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Audio\XAudio2\XAudio28Thread.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Audio\XAudio2\XAudio27Thread.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\rpcs3\Emu\Audio\XAudio2\XAudio2Thread.h">
|
||||
|
@ -102,13 +102,13 @@ cfg::map_entry<std::function<std::shared_ptr<GSRender>()>> g_cfg_gs_render(cfg::
|
||||
#endif
|
||||
});
|
||||
|
||||
cfg::map_entry<std::function<std::shared_ptr<AudioThread>()>> g_cfg_audio_render(cfg::root.audio, "Renderer", "OpenAL",
|
||||
cfg::map_entry<std::function<std::shared_ptr<AudioThread>()>> g_cfg_audio_render(cfg::root.audio, "Renderer", 1,
|
||||
{
|
||||
{ "Null", PURE_EXPR(std::make_shared<NullAudioThread>()) },
|
||||
{ "OpenAL", PURE_EXPR(std::make_shared<OpenALThread>()) },
|
||||
#ifdef _WIN32
|
||||
{ "XAudio2", PURE_EXPR(std::make_shared<XAudio2Thread>()) },
|
||||
#endif
|
||||
{ "OpenAL", PURE_EXPR(std::make_shared<OpenALThread>()) },
|
||||
});
|
||||
|
||||
extern cfg::bool_entry g_cfg_autostart;
|
||||
|
Loading…
Reference in New Issue
Block a user