зеркало из https://github.com/mozilla/gecko-dev.git
Bug 971695 - 2/2 - make YCbCrImageDataSerializer check data size - r=nical
This commit is contained in:
Родитель
9b3a0b05a4
Коммит
4aaf0bcadc
|
@ -51,70 +51,83 @@ struct YCbCrBufferInfo
|
|||
StereoMode mStereoMode;
|
||||
};
|
||||
|
||||
static YCbCrBufferInfo* GetYCbCrBufferInfo(uint8_t* aData)
|
||||
static YCbCrBufferInfo* GetYCbCrBufferInfo(uint8_t* aData, size_t aDataSize)
|
||||
{
|
||||
return reinterpret_cast<YCbCrBufferInfo*>(aData);
|
||||
return aDataSize >= sizeof(YCbCrBufferInfo)
|
||||
? reinterpret_cast<YCbCrBufferInfo*>(aData)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool YCbCrImageDataDeserializerBase::IsValid()
|
||||
void YCbCrImageDataDeserializerBase::Validate()
|
||||
{
|
||||
if (mData == nullptr) {
|
||||
return false;
|
||||
mIsValid = false;
|
||||
if (!mData) {
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
size_t requiredSize = ComputeMinBufferSize(
|
||||
IntSize(info->mYWidth, info->mYHeight),
|
||||
info->mYStride,
|
||||
IntSize(info->mCbCrWidth, info->mCbCrHeight),
|
||||
info->mCbCrStride);
|
||||
mIsValid = requiredSize <= mDataSize;
|
||||
|
||||
}
|
||||
|
||||
uint8_t* YCbCrImageDataDeserializerBase::GetYData()
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
return reinterpret_cast<uint8_t*>(info) + info->mYOffset;
|
||||
}
|
||||
|
||||
uint8_t* YCbCrImageDataDeserializerBase::GetCbData()
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
return reinterpret_cast<uint8_t*>(info) + info->mCbOffset;
|
||||
}
|
||||
|
||||
uint8_t* YCbCrImageDataDeserializerBase::GetCrData()
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
return reinterpret_cast<uint8_t*>(info) + info->mCrOffset;
|
||||
}
|
||||
|
||||
uint8_t* YCbCrImageDataDeserializerBase::GetData()
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
return (reinterpret_cast<uint8_t*>(info)) + MOZ_ALIGN_WORD(sizeof(YCbCrBufferInfo));
|
||||
}
|
||||
|
||||
uint32_t YCbCrImageDataDeserializerBase::GetYStride()
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
return info->mYStride;
|
||||
}
|
||||
|
||||
uint32_t YCbCrImageDataDeserializerBase::GetCbCrStride()
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
return info->mCbCrStride;
|
||||
}
|
||||
|
||||
gfx::IntSize YCbCrImageDataDeserializerBase::GetYSize()
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
return gfx::IntSize(info->mYWidth, info->mYHeight);
|
||||
}
|
||||
|
||||
gfx::IntSize YCbCrImageDataDeserializerBase::GetCbCrSize()
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
return gfx::IntSize(info->mCbCrWidth, info->mCbCrHeight);
|
||||
}
|
||||
|
||||
StereoMode YCbCrImageDataDeserializerBase::GetStereoMode()
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
return info->mStereoMode;
|
||||
}
|
||||
|
||||
|
@ -126,17 +139,24 @@ static size_t ComputeOffset(uint32_t aHeight, uint32_t aStride)
|
|||
|
||||
// Minimum required shmem size in bytes
|
||||
size_t
|
||||
YCbCrImageDataSerializer::ComputeMinBufferSize(const gfx::IntSize& aYSize,
|
||||
const gfx::IntSize& aCbCrSize)
|
||||
YCbCrImageDataDeserializerBase::ComputeMinBufferSize(const gfx::IntSize& aYSize,
|
||||
uint32_t aYStride,
|
||||
const gfx::IntSize& aCbCrSize,
|
||||
uint32_t aCbCrStride)
|
||||
{
|
||||
uint32_t yStride = aYSize.width;
|
||||
uint32_t CbCrStride = aCbCrSize.width;
|
||||
|
||||
return ComputeOffset(aYSize.height, yStride)
|
||||
+ 2 * ComputeOffset(aCbCrSize.height, CbCrStride)
|
||||
return ComputeOffset(aYSize.height, aYStride)
|
||||
+ 2 * ComputeOffset(aCbCrSize.height, aCbCrStride)
|
||||
+ MOZ_ALIGN_WORD(sizeof(YCbCrBufferInfo));
|
||||
}
|
||||
|
||||
// Minimum required shmem size in bytes
|
||||
size_t
|
||||
YCbCrImageDataDeserializerBase::ComputeMinBufferSize(const gfx::IntSize& aYSize,
|
||||
const gfx::IntSize& aCbCrSize)
|
||||
{
|
||||
return ComputeMinBufferSize(aYSize, aYSize.width, aCbCrSize, aCbCrSize.width);
|
||||
}
|
||||
|
||||
// Offset in bytes
|
||||
static size_t ComputeOffset(uint32_t aSize)
|
||||
{
|
||||
|
@ -145,7 +165,7 @@ static size_t ComputeOffset(uint32_t aSize)
|
|||
|
||||
// Minimum required shmem size in bytes
|
||||
size_t
|
||||
YCbCrImageDataSerializer::ComputeMinBufferSize(uint32_t aSize)
|
||||
YCbCrImageDataDeserializerBase::ComputeMinBufferSize(uint32_t aSize)
|
||||
{
|
||||
return ComputeOffset(aSize) + MOZ_ALIGN_WORD(sizeof(YCbCrBufferInfo));
|
||||
}
|
||||
|
@ -160,7 +180,8 @@ YCbCrImageDataSerializer::InitializeBufferInfo(uint32_t aYOffset,
|
|||
const gfx::IntSize& aCbCrSize,
|
||||
StereoMode aStereoMode)
|
||||
{
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData);
|
||||
YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData, mDataSize);
|
||||
MOZ_ASSERT(info); // OK to assert here, this method is client-side-only
|
||||
uint32_t info_size = MOZ_ALIGN_WORD(sizeof(YCbCrBufferInfo));
|
||||
info->mYOffset = info_size + aYOffset;
|
||||
info->mCbOffset = info_size + aCbOffset;
|
||||
|
@ -172,6 +193,7 @@ YCbCrImageDataSerializer::InitializeBufferInfo(uint32_t aYOffset,
|
|||
info->mCbCrWidth = aCbCrSize.width;
|
||||
info->mCbCrHeight = aCbCrSize.height;
|
||||
info->mStereoMode = aStereoMode;
|
||||
Validate();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -30,7 +30,7 @@ class Image;
|
|||
class YCbCrImageDataDeserializerBase
|
||||
{
|
||||
public:
|
||||
bool IsValid();
|
||||
bool IsValid() const { return mIsValid; }
|
||||
|
||||
/**
|
||||
* Returns the Y channel data pointer.
|
||||
|
@ -73,11 +73,32 @@ public:
|
|||
* Return a pointer to the begining of the data buffer.
|
||||
*/
|
||||
uint8_t* GetData();
|
||||
|
||||
/**
|
||||
* This function is meant as a helper to know how much shared memory we need
|
||||
* to allocate in a shmem in order to place a shared YCbCr image blob of
|
||||
* given dimensions.
|
||||
*/
|
||||
static size_t ComputeMinBufferSize(const gfx::IntSize& aYSize,
|
||||
uint32_t aYStride,
|
||||
const gfx::IntSize& aCbCrSize,
|
||||
uint32_t aCbCrStride);
|
||||
static size_t ComputeMinBufferSize(const gfx::IntSize& aYSize,
|
||||
const gfx::IntSize& aCbCrSize);
|
||||
static size_t ComputeMinBufferSize(uint32_t aSize);
|
||||
|
||||
protected:
|
||||
YCbCrImageDataDeserializerBase(uint8_t* aData)
|
||||
: mData (aData) {}
|
||||
YCbCrImageDataDeserializerBase(uint8_t* aData, size_t aDataSize)
|
||||
: mData (aData)
|
||||
, mDataSize(aDataSize)
|
||||
, mIsValid(false)
|
||||
{}
|
||||
|
||||
void Validate();
|
||||
|
||||
uint8_t* mData;
|
||||
size_t mDataSize;
|
||||
bool mIsValid;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -95,16 +116,12 @@ protected:
|
|||
class MOZ_STACK_CLASS YCbCrImageDataSerializer : public YCbCrImageDataDeserializerBase
|
||||
{
|
||||
public:
|
||||
YCbCrImageDataSerializer(uint8_t* aData) : YCbCrImageDataDeserializerBase(aData) {}
|
||||
|
||||
/**
|
||||
* This function is meant as a helper to know how much shared memory we need
|
||||
* to allocate in a shmem in order to place a shared YCbCr image blob of
|
||||
* given dimensions.
|
||||
*/
|
||||
static size_t ComputeMinBufferSize(const gfx::IntSize& aYSize,
|
||||
const gfx::IntSize& aCbCrSize);
|
||||
static size_t ComputeMinBufferSize(uint32_t aSize);
|
||||
YCbCrImageDataSerializer(uint8_t* aData, size_t aDataSize)
|
||||
: YCbCrImageDataDeserializerBase(aData, aDataSize)
|
||||
{
|
||||
// a serializer needs to be usable before correct buffer info has been written to it
|
||||
mIsValid = !!mData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the image informations in the buffer for given dimensions.
|
||||
|
@ -149,7 +166,11 @@ public:
|
|||
class MOZ_STACK_CLASS YCbCrImageDataDeserializer : public YCbCrImageDataDeserializerBase
|
||||
{
|
||||
public:
|
||||
YCbCrImageDataDeserializer(uint8_t* aData) : YCbCrImageDataDeserializerBase(aData) {}
|
||||
YCbCrImageDataDeserializer(uint8_t* aData, size_t aDataSize)
|
||||
: YCbCrImageDataDeserializerBase(aData, aDataSize)
|
||||
{
|
||||
Validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the YCbCr data into RGB and return a DataSourceSurface.
|
||||
|
|
|
@ -180,7 +180,8 @@ public:
|
|||
|
||||
bool ConvertImageToRGB(const SurfaceDescriptor& aImage)
|
||||
{
|
||||
YCbCrImageDataDeserializer deserializer(aImage.get_YCbCrImage().data().get<uint8_t>());
|
||||
YCbCrImageDataDeserializer deserializer(aImage.get_YCbCrImage().data().get<uint8_t>(),
|
||||
aImage.get_YCbCrImage().data().Size<uint8_t>());
|
||||
PlanarYCbCrData data;
|
||||
DeserializerToPlanarYCbCrImageData(deserializer, data);
|
||||
|
||||
|
|
|
@ -617,7 +617,7 @@ BufferTextureClient::UpdateYCbCr(const PlanarYCbCrData& aData)
|
|||
MOZ_ASSERT(IsValid());
|
||||
MOZ_ASSERT(aData.mCbSkip == aData.mCrSkip);
|
||||
|
||||
YCbCrImageDataSerializer serializer(GetBuffer());
|
||||
YCbCrImageDataSerializer serializer(GetBuffer(), GetBufferSize());
|
||||
MOZ_ASSERT(serializer.IsValid());
|
||||
if (!serializer.CopyData(aData.mYChannel, aData.mCbChannel, aData.mCrChannel,
|
||||
aData.mYSize, aData.mYStride,
|
||||
|
@ -647,7 +647,7 @@ BufferTextureClient::AllocateForYCbCr(gfx::IntSize aYSize,
|
|||
if (!Allocate(bufSize)) {
|
||||
return false;
|
||||
}
|
||||
YCbCrImageDataSerializer serializer(GetBuffer());
|
||||
YCbCrImageDataSerializer serializer(GetBuffer(), GetBufferSize());
|
||||
serializer.InitializeBufferInfo(aYSize,
|
||||
aCbCrSize,
|
||||
aStereoMode);
|
||||
|
@ -970,7 +970,7 @@ AutoLockYCbCrClient::Update(PlanarYCbCrImage* aImage)
|
|||
|
||||
ipc::Shmem& shmem = mDescriptor->get_YCbCrImage().data();
|
||||
|
||||
YCbCrImageDataSerializer serializer(shmem.get<uint8_t>());
|
||||
YCbCrImageDataSerializer serializer(shmem.get<uint8_t>(), shmem.Size<uint8_t>());
|
||||
if (!serializer.CopyData(data->mYChannel, data->mCbChannel, data->mCrChannel,
|
||||
data->mYSize, data->mYStride,
|
||||
data->mCbCrSize, data->mCbCrStride,
|
||||
|
@ -999,7 +999,7 @@ bool AutoLockYCbCrClient::EnsureDeprecatedTextureClient(PlanarYCbCrImage* aImage
|
|||
needsAllocation = true;
|
||||
} else {
|
||||
ipc::Shmem& shmem = mDescriptor->get_YCbCrImage().data();
|
||||
YCbCrImageDataSerializer serializer(shmem.get<uint8_t>());
|
||||
YCbCrImageDataSerializer serializer(shmem.get<uint8_t>(), shmem.Size<uint8_t>());
|
||||
if (serializer.GetYSize() != data->mYSize ||
|
||||
serializer.GetCbCrSize() != data->mCbCrSize) {
|
||||
needsAllocation = true;
|
||||
|
@ -1020,7 +1020,7 @@ bool AutoLockYCbCrClient::EnsureDeprecatedTextureClient(PlanarYCbCrImage* aImage
|
|||
return false;
|
||||
}
|
||||
|
||||
YCbCrImageDataSerializer serializer(shmem.get<uint8_t>());
|
||||
YCbCrImageDataSerializer serializer(shmem.get<uint8_t>(), shmem.Size<uint8_t>());
|
||||
serializer.InitializeBufferInfo(data->mYSize,
|
||||
data->mCbCrSize,
|
||||
data->mStereoMode);
|
||||
|
|
|
@ -494,7 +494,7 @@ BufferTextureHost::Upload(nsIntRegion *aRegion)
|
|||
NS_WARNING("BufferTextureHost: unsupported format!");
|
||||
return false;
|
||||
} else if (mFormat == gfx::SurfaceFormat::YUV) {
|
||||
YCbCrImageDataDeserializer yuvDeserializer(GetBuffer());
|
||||
YCbCrImageDataDeserializer yuvDeserializer(GetBuffer(), GetBufferSize());
|
||||
MOZ_ASSERT(yuvDeserializer.IsValid());
|
||||
|
||||
if (!mCompositor->SupportsEffect(EFFECT_YCBCR)) {
|
||||
|
@ -585,7 +585,7 @@ BufferTextureHost::GetAsSurface()
|
|||
NS_WARNING("BufferTextureHost: unsupported format!");
|
||||
return nullptr;
|
||||
} else if (mFormat == gfx::SurfaceFormat::YUV) {
|
||||
YCbCrImageDataDeserializer yuvDeserializer(GetBuffer());
|
||||
YCbCrImageDataDeserializer yuvDeserializer(GetBuffer(), GetBufferSize());
|
||||
if (!yuvDeserializer.IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -896,7 +896,8 @@ DeprecatedTextureHostYCbCrD3D11::UpdateImpl(const SurfaceDescriptor& aImage,
|
|||
{
|
||||
MOZ_ASSERT(aImage.type() == SurfaceDescriptor::TYCbCrImage);
|
||||
|
||||
YCbCrImageDataDeserializer yuvDeserializer(aImage.get_YCbCrImage().data().get<uint8_t>());
|
||||
YCbCrImageDataDeserializer yuvDeserializer(aImage.get_YCbCrImage().data().get<uint8_t>(),
|
||||
aImage.get_YCbCrImage().data().Size<uint8_t>());
|
||||
|
||||
gfx::IntSize gfxCbCrSize = yuvDeserializer.GetCbCrSize();
|
||||
|
||||
|
|
|
@ -442,7 +442,8 @@ DeprecatedTextureHostYCbCrD3D9::UpdateImpl(const SurfaceDescriptor& aImage,
|
|||
return;
|
||||
}
|
||||
|
||||
YCbCrImageDataDeserializer yuvDeserializer(aImage.get_YCbCrImage().data().get<uint8_t>());
|
||||
YCbCrImageDataDeserializer yuvDeserializer(aImage.get_YCbCrImage().data().get<uint8_t>(),
|
||||
aImage.get_YCbCrImage().data().Size<uint8_t>());
|
||||
|
||||
mSize = yuvDeserializer.GetYSize();
|
||||
IntSize cbCrSize = yuvDeserializer.GetCbCrSize();
|
||||
|
|
|
@ -125,7 +125,7 @@ SharedPlanarYCbCrImage::SetData(const PlanarYCbCrData& aData)
|
|||
mData.mCbCrSize);
|
||||
mSize = mData.mPicSize;
|
||||
|
||||
YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer());
|
||||
YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer(), mTextureClient->GetBufferSize());
|
||||
mData.mYChannel = serializer.GetYData();
|
||||
mData.mCbChannel = serializer.GetCbData();
|
||||
mData.mCrChannel = serializer.GetCrData();
|
||||
|
@ -148,7 +148,7 @@ SharedPlanarYCbCrImage::AllocateAndGetNewBuffer(uint32_t aSize)
|
|||
// update buffer size
|
||||
mBufferSize = size;
|
||||
|
||||
YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer());
|
||||
YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer(), mTextureClient->GetBufferSize());
|
||||
return serializer.GetData();
|
||||
}
|
||||
|
||||
|
@ -163,7 +163,7 @@ SharedPlanarYCbCrImage::SetDataNoCopy(const Data &aData)
|
|||
* with AllocateAndGetNewBuffer(), that we subtract from the Y, Cb, Cr
|
||||
* channels to compute 0-based offsets to pass to InitializeBufferInfo.
|
||||
*/
|
||||
YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer());
|
||||
YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer(), mTextureClient->GetBufferSize());
|
||||
uint8_t *base = serializer.GetData();
|
||||
uint32_t yOffset = aData.mYChannel - base;
|
||||
uint32_t cbOffset = aData.mCbChannel - base;
|
||||
|
@ -207,7 +207,7 @@ SharedPlanarYCbCrImage::Allocate(PlanarYCbCrData& aData)
|
|||
return false;
|
||||
}
|
||||
|
||||
YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer());
|
||||
YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer(), mTextureClient->GetBufferSize());
|
||||
serializer.InitializeBufferInfo(aData.mYSize,
|
||||
aData.mCbCrSize,
|
||||
aData.mStereoMode);
|
||||
|
@ -258,7 +258,7 @@ DeprecatedSharedPlanarYCbCrImage::SetData(const PlanarYCbCrData& aData)
|
|||
mData.mCbCrSize);
|
||||
mSize = mData.mPicSize;
|
||||
|
||||
YCbCrImageDataSerializer serializer(mShmem.get<uint8_t>());
|
||||
YCbCrImageDataSerializer serializer(mShmem.get<uint8_t>(), mShmem.Size<uint8_t>());
|
||||
MOZ_ASSERT(aData.mCbSkip == aData.mCrSkip);
|
||||
if (!serializer.CopyData(aData.mYChannel, aData.mCbChannel, aData.mCrChannel,
|
||||
aData.mYSize, aData.mYStride,
|
||||
|
@ -287,7 +287,7 @@ DeprecatedSharedPlanarYCbCrImage::AllocateAndGetNewBuffer(uint32_t aSize)
|
|||
// update buffer size
|
||||
mBufferSize = size;
|
||||
|
||||
YCbCrImageDataSerializer serializer(mShmem.get<uint8_t>());
|
||||
YCbCrImageDataSerializer serializer(mShmem.get<uint8_t>(), mShmem.Size<uint8_t>());
|
||||
return serializer.GetData();
|
||||
}
|
||||
|
||||
|
@ -296,7 +296,7 @@ DeprecatedSharedPlanarYCbCrImage::SetDataNoCopy(const Data &aData)
|
|||
{
|
||||
mData = aData;
|
||||
mSize = aData.mPicSize;
|
||||
YCbCrImageDataSerializer serializer(mShmem.get<uint8_t>());
|
||||
YCbCrImageDataSerializer serializer(mShmem.get<uint8_t>(), mShmem.Size<uint8_t>());
|
||||
serializer.InitializeBufferInfo(aData.mYSize,
|
||||
aData.mCbCrSize,
|
||||
aData.mStereoMode);
|
||||
|
@ -327,7 +327,7 @@ DeprecatedSharedPlanarYCbCrImage::Allocate(PlanarYCbCrData& aData)
|
|||
return false;
|
||||
}
|
||||
|
||||
YCbCrImageDataSerializer serializer(mShmem.get<uint8_t>());
|
||||
YCbCrImageDataSerializer serializer(mShmem.get<uint8_t>(), mShmem.Size<uint8_t>());
|
||||
serializer.InitializeBufferInfo(aData.mYSize,
|
||||
aData.mCbCrSize,
|
||||
aData.mStereoMode);
|
||||
|
|
|
@ -181,7 +181,7 @@ void TestTextureClientYCbCr(TextureClient* client, PlanarYCbCrData& ycbcrData) {
|
|||
// This will work iff the compositor is not BasicCompositor
|
||||
ASSERT_EQ(host->GetFormat(), mozilla::gfx::SurfaceFormat::YUV);
|
||||
|
||||
YCbCrImageDataDeserializer yuvDeserializer(host->GetBuffer());
|
||||
YCbCrImageDataDeserializer yuvDeserializer(host->GetBuffer(), host->GetBufferSize());
|
||||
ASSERT_TRUE(yuvDeserializer.IsValid());
|
||||
PlanarYCbCrData data;
|
||||
data.mYChannel = yuvDeserializer.GetYData();
|
||||
|
|
Загрузка…
Ссылка в новой задаче