/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "htmlreqifreader.hxx"
#include <comphelper/scopeguard.hxx>
#include <filter/msfilter/rtfutil.hxx>
#include <rtl/strbuf.hxx>
#include <sot/storage.hxx>
#include <svtools/parrtf.hxx>
#include <svtools/rtfkeywd.hxx>
#include <svtools/rtftoken.h>
#include <tools/stream.hxx>
#include <filter/msfilter/msdffimp.hxx>
#include <vcl/cvtgrf.hxx>
#include <ndole.hxx>
#include <sal/log.hxx>
#include <vcl/FilterConfigItem.hxx>
#include <vcl/wmf.hxx>
#include <comphelper/propertyvalue.hxx>
#include <fmtfsize.hxx>
#include <frmfmt.hxx>
using namespace com::sun::star;
namespace
{
/// RTF parser that just extracts a single OLE2 object from a file.
class ReqIfRtfReader : public SvRTFParser
{
public:
ReqIfRtfReader(SvStream& rStream);
void NextToken(int nToken) override;
bool WriteObjectData(SvStream& rOLE);
private:
bool m_bInObjData = false;
OStringBuffer m_aHex;
};
ReqIfRtfReader::ReqIfRtfReader(SvStream& rStream)
: SvRTFParser(rStream)
{
}
void ReqIfRtfReader::NextToken(int nToken)
{
switch (nToken)
{
case '}':
m_bInObjData = false;
break;
case RTF_TEXTTOKEN:
if (m_bInObjData)
m_aHex.append(OUStringToOString(aToken, RTL_TEXTENCODING_ASCII_US));
break;
case RTF_OBJDATA:
m_bInObjData = true;
break;
}
}
bool ReqIfRtfReader::WriteObjectData(SvStream& rOLE)
{
return msfilter::rtfutil::ExtractOLE2FromObjdata(m_aHex.makeStringAndClear(), rOLE);
}
/// Looks up what OLE1 calls the ClassName, see [MS-OLEDS] 2.3.8 CompObjStream.
OString ExtractOLEClassName(const rtl::Reference<SotStorage>& xStorage)
{
OString aRet;
rtl::Reference<SotStorageStream> pCompObj = xStorage->OpenSotStream(u"\1CompObj"_ustr);
if (!pCompObj)
return aRet;
pCompObj->Seek(0);
pCompObj->SeekRel(28); // Header
if (!pCompObj->good())
return aRet;
sal_uInt32 nData;
pCompObj->ReadUInt32(nData); // AnsiUserType
pCompObj->SeekRel(nData);
if (!pCompObj->good())
return aRet;
pCompObj->ReadUInt32(nData); // AnsiClipboardFormat
pCompObj->SeekRel(nData);
if (!pCompObj->good())
return aRet;
pCompObj->ReadUInt32(nData); // Reserved1
return read_uInt8s_ToOString(*pCompObj, nData - 1); // -1 because it is null-terminated
}
/// Parses the presentation stream of an OLE2 storage.
bool ParseOLE2Presentation(SvStream& rOle2, sal_uInt32& nWidth, sal_uInt32& nHeight,
SvStream& rPresentationData)
{
// See [MS-OLEDS] 2.3.4, OLEPresentationStream
rOle2.Seek(0);
rtl::Reference<SotStorage> pStorage = new SotStorage(rOle2);
rtl::Reference<SotStorageStream> xOle2Presentation
= pStorage->OpenSotStream(u"\002OlePres000"_ustr, StreamMode::STD_READ);
// Read AnsiClipboardFormat.
sal_uInt32 nMarkerOrLength = 0;
xOle2Presentation->ReadUInt32(nMarkerOrLength);
if (nMarkerOrLength != 0xffffffff)
// FormatOrAnsiString is not present
return false;
sal_uInt32 nFormatOrAnsiLength = 0;
xOle2Presentation->ReadUInt32(nFormatOrAnsiLength);
if (nFormatOrAnsiLength != 0x00000003) // CF_METAFILEPICT
return false;
// Read TargetDeviceSize.
sal_uInt32 nTargetDeviceSize = 0;
xOle2Presentation->ReadUInt32(nTargetDeviceSize);
if (nTargetDeviceSize != 0x00000004)
// TargetDevice is present
return false;
sal_uInt32 nAspect = 0;
xOle2Presentation->ReadUInt32(nAspect);
sal_uInt32 nLindex = 0;
xOle2Presentation->ReadUInt32(nLindex);
sal_uInt32 nAdvf = 0;
xOle2Presentation->ReadUInt32(nAdvf);
sal_uInt32 nReserved1 = 0;
xOle2Presentation->ReadUInt32(nReserved1);
xOle2Presentation->ReadUInt32(nWidth);
xOle2Presentation->ReadUInt32(nHeight);
sal_uInt32 nSize = 0;
xOle2Presentation->ReadUInt32(nSize);
// Read Data.
if (nSize > xOle2Presentation->remainingSize())
return false;
if (nSize <= 64)
{
SAL_WARN("sw.html",
"ParseOLE2Presentation: ignoring potentially broken small preview: size is "
<< nSize);
return false;
}
std::vector<char> aBuffer(nSize);
xOle2Presentation->ReadBytes(aBuffer.data(), aBuffer.size());
rPresentationData.WriteBytes(aBuffer.data(), aBuffer.size());
return true;
}
/**
* Inserts an OLE1 header before an OLE2 storage, assuming that the storage has an Ole10Native
* stream.
*/
OString InsertOLE1HeaderFromOle10NativeStream(const rtl::Reference<SotStorage>& xStorage,
SwOLENode& rOLENode, SvStream& rOle1)
{
rtl::Reference<SotStorageStream> xOle1Stream
= xStorage->OpenSotStream(u"\1Ole10Native"_ustr, StreamMode::STD_READ);
sal_uInt32 nOle1Size = 0;
xOle1Stream->ReadUInt32(nOle1Size);
OString aClassName;
if (xStorage->GetClassName() == SvGlobalName(0x0003000A, 0, 0, 0xc0, 0, 0, 0, 0, 0, 0, 0x46))
{
aClassName = "PBrush"_ostr;
}
else
{
if (xStorage->GetClassName()
!= SvGlobalName(0x0003000C, 0, 0, 0xc0, 0, 0, 0, 0, 0, 0, 0x46))
{
SAL_WARN("sw.html", "InsertOLE1HeaderFromOle10NativeStream: unexpected class id: "
<< xStorage->GetClassName().GetHexName());
}
aClassName = "Package"_ostr;
}
// Write ObjectHeader, see [MS-OLEDS] 2.2.4.
rOle1.Seek(0);
// OLEVersion.
rOle1.WriteUInt32(0x00000501);
// FormatID is EmbeddedObject.
rOle1.WriteUInt32(0x00000002);
// ClassName
rOle1.WriteUInt32(aClassName.isEmpty() ? 0 : aClassName.getLength() + 1);
if (!aClassName.isEmpty())
{
rOle1.WriteOString(aClassName);
// Null terminated pascal string.
rOle1.WriteChar(0);
}
// TopicName.
rOle1.WriteUInt32(0);
// ItemName.
rOle1.WriteUInt32(0);
// NativeDataSize
rOle1.WriteUInt32(nOle1Size);
// Write the actual native data.
rOle1.WriteStream(*xOle1Stream, nOle1Size);
// Write Presentation.
if (!rOLENode.GetGraphic())
{
return aClassName;
}
const Graphic* pGraphic = rOLENode.GetGraphic();
const Graphic rGraphic = pGraphic ? *pGraphic : Graphic();
Size aSize = rOLENode.GetTwipSize();
SvMemoryStream aGraphicStream;
if (GraphicConverter::Export(aGraphicStream, rGraphic, ConvertDataFormat::WMF) != ERRCODE_NONE)
{
return aClassName;
}
auto pGraphicAry = static_cast<const sal_uInt8*>(aGraphicStream.GetData());
sal_uInt64 nPresentationData = aGraphicStream.TellEnd();
msfilter::rtfutil::StripMetafileHeader(pGraphicAry, nPresentationData);
// OLEVersion.
rOle1.WriteUInt32(0x00000501);
// FormatID: constant means the ClassName field is present.
rOle1.WriteUInt32(0x00000005);
// ClassName: null terminated pascal string.
OString aPresentationClassName("METAFILEPICT"_ostr);
rOle1.WriteUInt32(aPresentationClassName.getLength() + 1);
rOle1.WriteOString(aPresentationClassName);
rOle1.WriteChar(0);
// Width.
rOle1.WriteUInt32(aSize.getWidth());
// Height.
rOle1.WriteUInt32(aSize.getHeight() * -1);
// PresentationDataSize
rOle1.WriteUInt32(8 + nPresentationData);
// Reserved1-4.
rOle1.WriteUInt16(0x0008);
rOle1.WriteUInt16(0x31b1);
rOle1.WriteUInt16(0x1dd9);
rOle1.WriteUInt16(0x0000);
rOle1.WriteBytes(pGraphicAry, nPresentationData);
return aClassName;
}
/**
* Writes an OLE1 header and data from rOle2 to rOle1.
*
* In case rOle2 has presentation data, then its size is written to nWidth/nHeight. Otherwise
* nWidth/nHeight/pPresentationData/nPresentationData is used for the presentation data.
*/
OString InsertOLE1Header(SvStream& rOle2, SvStream& rOle1, sal_uInt32& nWidth, sal_uInt32& nHeight,
SwOLENode& rOLENode, const sal_uInt8* pPresentationData,
sal_uInt64 nPresentationData)
{
rOle2.Seek(0);
rtl::Reference<SotStorage> xStorage(new SotStorage(rOle2));
if (xStorage->GetError() != ERRCODE_NONE)
return {};
if (xStorage->IsStream(u"\1Ole10Native"_ustr))
{
return InsertOLE1HeaderFromOle10NativeStream(xStorage, rOLENode, rOle1);
}
OString aClassName = ExtractOLEClassName(xStorage);
// Write ObjectHeader, see [MS-OLEDS] 2.2.4.
rOle1.Seek(0);
// OLEVersion.
rOle1.WriteUInt32(0x00000501);
// FormatID is EmbeddedObject.
rOle1.WriteUInt32(0x00000002);
// ClassName
rOle1.WriteUInt32(aClassName.isEmpty() ? 0 : aClassName.getLength() + 1);
if (!aClassName.isEmpty())
{
rOle1.WriteOString(aClassName);
// Null terminated pascal string.
rOle1.WriteChar(0);
}
// TopicName.
rOle1.WriteUInt32(0);
// ItemName.
rOle1.WriteUInt32(0);
// NativeDataSize
rOle1.WriteUInt32(rOle2.TellEnd());
// Write the actual native data.
rOle2.Seek(0);
rOle1.WriteStream(rOle2);
// Write Presentation.
SvMemoryStream aPresentationData;
// OLEVersion.
rOle1.WriteUInt32(0x00000501);
// FormatID: constant means the ClassName field is present.
rOle1.WriteUInt32(0x00000005);
// ClassName: null terminated pascal string.
OString aPresentationClassName("METAFILEPICT"_ostr);
rOle1.WriteUInt32(aPresentationClassName.getLength() + 1);
rOle1.WriteOString(aPresentationClassName);
rOle1.WriteChar(0);
const sal_uInt8* pBytes = nullptr;
sal_uInt64 nBytes = 0;
if (ParseOLE2Presentation(rOle2, nWidth, nHeight, aPresentationData))
{
// Take presentation data for OLE1 from OLE2.
pBytes = static_cast<const sal_uInt8*>(aPresentationData.GetData());
nBytes = aPresentationData.Tell();
}
else
{
// Take presentation data for OLE1 from RTF.
pBytes = pPresentationData;
nBytes = nPresentationData;
}
// Width.
rOle1.WriteUInt32(nWidth);
// Height.
rOle1.WriteUInt32(nHeight * -1);
// PresentationDataSize: size of (reserved fields + pBytes).
rOle1.WriteUInt32(8 + nBytes);
// Reserved1-4.
rOle1.WriteUInt16(0x0008);
rOle1.WriteUInt16(0x31b1);
rOle1.WriteUInt16(0x1dd9);
rOle1.WriteUInt16(0x0000);
rOle1.WriteBytes(pBytes, nBytes);
return aClassName;
}
/// Writes presentation data with the specified size to rRtf as an RTF hexdump.
void WrapOleGraphicInRtf(SvStream& rRtf, sal_uInt32 nWidth, sal_uInt32 nHeight,
const sal_uInt8* pPresentationData, sal_uInt64 nPresentationData)
{
// Start result.
rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_RESULT);
// Start pict.
rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_PICT);
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_WMETAFILE "8");
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICW);
rRtf.WriteOString(OString::number(nWidth));
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICH);
rRtf.WriteOString(OString::number(nHeight));
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICWGOAL);
rRtf.WriteOString(OString::number(nWidth));
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICHGOAL);
rRtf.WriteOString(OString::number(nHeight));
if (pPresentationData)
{
rRtf.WriteOString(SAL_NEWLINE_STRING);
msfilter::rtfutil::WriteHex(pPresentationData, nPresentationData, &rRtf);
}
// End pict.
rRtf.WriteOString("}");
// End result.
rRtf.WriteOString("}");
}
}
namespace SwReqIfReader
{
bool ExtractOleFromRtf(SvStream& rRtf, SvStream& rOle, bool& bOwnFormat)
{
// Add missing header/footer.
SvMemoryStream aRtf;
aRtf.WriteOString("{\\rtf1");
aRtf.WriteStream(rRtf);
aRtf.WriteOString("}");
aRtf.Seek(0);
// Read the RTF markup.
tools::SvRef<ReqIfRtfReader> xReader(new ReqIfRtfReader(aRtf));
SvParserState eState = xReader->CallParser();
if (eState == SvParserState::Error)
return false;
// Write the OLE2 data.
if (!xReader->WriteObjectData(rOle))
return false;
rtl::Reference<SotStorage> pStorage = new SotStorage(rOle);
OUString aFilterName = SvxMSDffManager::GetFilterNameFromClassID(pStorage->GetClassName());
bOwnFormat = !aFilterName.isEmpty();
if (!bOwnFormat)
{
// Real OLE2 data, we're done.
rOle.Seek(0);
return true;
}
// ODF-in-OLE2 case, extract actual data.
SvMemoryStream aMemory;
SvxMSDffManager::ExtractOwnStream(*pStorage, aMemory);
rOle.Seek(0);
aMemory.Seek(0);
rOle.WriteStream(aMemory);
// Stream length is current position + 1.
rOle.SetStreamSize(aMemory.GetSize() + 1);
rOle.Seek(0);
return true;
}
bool WrapOleInRtf(SvStream& rOle2, SvStream& rRtf, SwOLENode& rOLENode,
const SwFrameFormat& rFormat)
{
sal_uInt64 nPos = rOle2.Tell();
comphelper::ScopeGuard g([&rOle2, nPos] { rOle2.Seek(nPos); });
// Write OLE1 header, then the RTF wrapper.
SvMemoryStream aOLE1;
// Prepare presentation data early, so it's available to both OLE1 and RTF.
Size aSize = rFormat.GetFrameSize().GetSize();
sal_uInt32 nWidth = aSize.getWidth();
sal_uInt32 nHeight = aSize.getHeight();
const Graphic* pGraphic = rOLENode.GetGraphic();
const sal_uInt8* pPresentationData = nullptr;
sal_uInt64 nPresentationData = 0;
SvMemoryStream aGraphicStream;
if (pGraphic)
{
uno::Sequence<beans::PropertyValue> aFilterData
= { comphelper::makePropertyValue(u"EmbedEMF"_ustr, false) };
FilterConfigItem aConfigItem(&aFilterData);
if (ConvertGraphicToWMF(*pGraphic, aGraphicStream, &aConfigItem))
{
pPresentationData = static_cast<const sal_uInt8*>(aGraphicStream.GetData());
nPresentationData = aGraphicStream.TellEnd();
msfilter::rtfutil::StripMetafileHeader(pPresentationData, nPresentationData);
}
}
OString aClassName = InsertOLE1Header(rOle2, aOLE1, nWidth, nHeight, rOLENode,
pPresentationData, nPresentationData);
// Start object.
rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_OBJECT);
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJEMB);
// Start objclass.
rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_OBJCLASS " ");
rRtf.WriteOString(aClassName);
// End objclass.
rRtf.WriteOString("}");
// Object size.
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJW);
rRtf.WriteOString(OString::number(nWidth));
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJH);
rRtf.WriteOString(OString::number(nHeight));
// Start objdata.
rRtf.WriteOString(
"{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_OBJDATA SAL_NEWLINE_STRING);
msfilter::rtfutil::WriteHex(static_cast<const sal_uInt8*>(aOLE1.GetData()), aOLE1.GetSize(),
&rRtf);
// End objdata.
rRtf.WriteOString("}");
if (pPresentationData)
{
WrapOleGraphicInRtf(rRtf, nWidth, nHeight, pPresentationData, nPresentationData);
}
// End object.
rRtf.WriteOString("}");
return true;
}
bool WrapGraphicInRtf(const Graphic& rGraphic, const SwFrameFormat& rFormat, SvStream& rRtf)
{
// Start object.
rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_OBJECT);
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJEMB);
// Object size: as used in the document model (not pixel size)
Size aSize = rFormat.GetFrameSize().GetSize();
sal_uInt32 nWidth = aSize.getWidth();
sal_uInt32 nHeight = aSize.getHeight();
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJW);
rRtf.WriteOString(OString::number(nWidth));
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_OBJH);
rRtf.WriteOString(OString::number(nHeight));
rRtf.WriteOString(SAL_NEWLINE_STRING);
// Start objclass.
rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_OBJCLASS " ");
OString aClassName("PBrush"_ostr);
rRtf.WriteOString(aClassName);
// End objclass.
rRtf.WriteOString("}");
rRtf.WriteOString(SAL_NEWLINE_STRING);
// Start objdata.
rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_OBJDATA " ");
SvMemoryStream aOle1;
// Write ObjectHeader, see [MS-OLEDS] 2.2.4.
// OLEVersion.
aOle1.WriteUInt32(0x00000501);
// FormatID is EmbeddedObject.
aOle1.WriteUInt32(0x00000002);
// ClassName
aOle1.WriteUInt32(aClassName.getLength() + 1);
aOle1.WriteOString(aClassName);
// Null terminated pascal string.
aOle1.WriteChar(0);
// TopicName.
aOle1.WriteUInt32(0);
// ItemName.
aOle1.WriteUInt32(0);
// NativeDataSize
SvMemoryStream aNativeData;
// Set white background for the semi-transparent pixels.
BitmapEx aBitmapEx = rGraphic.GetBitmapEx();
Bitmap aBitmap = aBitmapEx.GetBitmap(/*aTransparentReplaceColor=*/COL_WHITE);
if (aBitmap.getPixelFormat() != vcl::PixelFormat::N24_BPP)
{
// More exotic pixel formats cause trouble for ms paint.
aBitmap.Convert(BmpConversion::N24Bit);
}
if (GraphicConverter::Export(aNativeData, BitmapEx(aBitmap), ConvertDataFormat::BMP)
!= ERRCODE_NONE)
{
SAL_WARN("sw.html", "WrapGraphicInRtf: bmp conversion failed");
}
aOle1.WriteUInt32(aNativeData.TellEnd());
// Write the actual native data.
aNativeData.Seek(0);
aOle1.WriteStream(aNativeData);
// Prepare presentation data.
const sal_uInt8* pPresentationData = nullptr;
sal_uInt64 nPresentationData = 0;
SvMemoryStream aGraphicStream;
uno::Sequence<beans::PropertyValue> aFilterData
= { comphelper::makePropertyValue(u"EmbedEMF"_ustr, false) };
FilterConfigItem aConfigItem(&aFilterData);
if (ConvertGraphicToWMF(rGraphic, aGraphicStream, &aConfigItem))
{
pPresentationData = static_cast<const sal_uInt8*>(aGraphicStream.GetData());
nPresentationData = aGraphicStream.TellEnd();
msfilter::rtfutil::StripMetafileHeader(pPresentationData, nPresentationData);
}
// Write Presentation.
// OLEVersion.
aOle1.WriteUInt32(0x00000501);
// FormatID: constant means the ClassName field is present.
aOle1.WriteUInt32(0x00000005);
// ClassName: null terminated pascal string.
OString aPresentationClassName("METAFILEPICT"_ostr);
aOle1.WriteUInt32(aPresentationClassName.getLength() + 1);
aOle1.WriteOString(aPresentationClassName);
aOle1.WriteChar(0);
const sal_uInt8* pBytes = nullptr;
sal_uInt64 nBytes = 0;
// Take presentation data for OLE1 from RTF.
pBytes = pPresentationData;
nBytes = nPresentationData;
// Width.
aOle1.WriteUInt32(nWidth);
// Height.
aOle1.WriteUInt32(nHeight * -1);
// PresentationDataSize: size of (reserved fields + pBytes).
aOle1.WriteUInt32(8 + nBytes);
// Reserved1-4.
aOle1.WriteUInt16(0x0008);
aOle1.WriteUInt16(0x31b1);
aOle1.WriteUInt16(0x1dd9);
aOle1.WriteUInt16(0x0000);
aOle1.WriteBytes(pBytes, nBytes);
// End objdata.
msfilter::rtfutil::WriteHex(static_cast<const sal_uInt8*>(aOle1.GetData()), aOle1.GetSize(),
&rRtf);
rRtf.WriteOString("}");
rRtf.WriteOString(SAL_NEWLINE_STRING);
rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_RESULT);
rRtf.WriteOString("{" OOO_STRING_SVTOOLS_RTF_PICT);
Size aMapped(rGraphic.GetPrefSize());
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICW);
rRtf.WriteOString(OString::number(aMapped.Width()));
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICH);
rRtf.WriteOString(OString::number(aMapped.Height()));
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICWGOAL);
rRtf.WriteOString(OString::number(nWidth));
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_PICHGOAL);
rRtf.WriteOString(OString::number(nHeight));
rRtf.WriteOString(OOO_STRING_SVTOOLS_RTF_WMETAFILE "8");
rRtf.WriteOString(SAL_NEWLINE_STRING);
if (pPresentationData)
{
msfilter::rtfutil::WriteHex(pPresentationData, nPresentationData, &rRtf);
rRtf.WriteOString(SAL_NEWLINE_STRING);
}
// End pict.
rRtf.WriteOString("}");
// End result.
rRtf.WriteOString("}");
// End object.
rRtf.WriteOString("}");
return true;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'WriteHex' is required to be utilized.
↑ V530 The return value of function 'WriteHex' is required to be utilized.
↑ V530 The return value of function 'WriteHex' is required to be utilized.
↑ V530 The return value of function 'WriteHex' is required to be utilized.