1*f6dc9357SAndroid Build Coastguard Worker // NsisDecode.cpp
2*f6dc9357SAndroid Build Coastguard Worker
3*f6dc9357SAndroid Build Coastguard Worker #include "StdAfx.h"
4*f6dc9357SAndroid Build Coastguard Worker
5*f6dc9357SAndroid Build Coastguard Worker #include "../../../../C/CpuArch.h"
6*f6dc9357SAndroid Build Coastguard Worker
7*f6dc9357SAndroid Build Coastguard Worker #include "NsisDecode.h"
8*f6dc9357SAndroid Build Coastguard Worker
9*f6dc9357SAndroid Build Coastguard Worker #include "../../Common/CreateCoder.h"
10*f6dc9357SAndroid Build Coastguard Worker #include "../../Common/LimitedStreams.h"
11*f6dc9357SAndroid Build Coastguard Worker #include "../../Common/MethodId.h"
12*f6dc9357SAndroid Build Coastguard Worker
13*f6dc9357SAndroid Build Coastguard Worker #include "../../Compress/BcjCoder.h"
14*f6dc9357SAndroid Build Coastguard Worker
15*f6dc9357SAndroid Build Coastguard Worker #define Get32(p) GetUi32(p)
16*f6dc9357SAndroid Build Coastguard Worker
17*f6dc9357SAndroid Build Coastguard Worker namespace NArchive {
18*f6dc9357SAndroid Build Coastguard Worker namespace NNsis {
19*f6dc9357SAndroid Build Coastguard Worker
GetInputProcessedSize() const20*f6dc9357SAndroid Build Coastguard Worker UInt64 CDecoder::GetInputProcessedSize() const
21*f6dc9357SAndroid Build Coastguard Worker {
22*f6dc9357SAndroid Build Coastguard Worker if (_lzmaDecoder)
23*f6dc9357SAndroid Build Coastguard Worker return _lzmaDecoder->GetInputProcessedSize();
24*f6dc9357SAndroid Build Coastguard Worker if (_deflateDecoder)
25*f6dc9357SAndroid Build Coastguard Worker return _deflateDecoder->GetInputProcessedSize();
26*f6dc9357SAndroid Build Coastguard Worker if (_bzDecoder)
27*f6dc9357SAndroid Build Coastguard Worker return _bzDecoder->GetInputProcessedSize();
28*f6dc9357SAndroid Build Coastguard Worker return 0;
29*f6dc9357SAndroid Build Coastguard Worker }
30*f6dc9357SAndroid Build Coastguard Worker
31*f6dc9357SAndroid Build Coastguard Worker
Init(ISequentialInStream * inStream,bool & useFilter)32*f6dc9357SAndroid Build Coastguard Worker HRESULT CDecoder::Init(ISequentialInStream *inStream, bool &useFilter)
33*f6dc9357SAndroid Build Coastguard Worker {
34*f6dc9357SAndroid Build Coastguard Worker useFilter = false;
35*f6dc9357SAndroid Build Coastguard Worker
36*f6dc9357SAndroid Build Coastguard Worker if (_decoderInStream)
37*f6dc9357SAndroid Build Coastguard Worker if (Method != _curMethod)
38*f6dc9357SAndroid Build Coastguard Worker Release();
39*f6dc9357SAndroid Build Coastguard Worker _curMethod = Method;
40*f6dc9357SAndroid Build Coastguard Worker
41*f6dc9357SAndroid Build Coastguard Worker if (!_codecInStream)
42*f6dc9357SAndroid Build Coastguard Worker {
43*f6dc9357SAndroid Build Coastguard Worker switch ((int)Method)
44*f6dc9357SAndroid Build Coastguard Worker {
45*f6dc9357SAndroid Build Coastguard Worker // case NMethodType::kCopy: return E_NOTIMPL;
46*f6dc9357SAndroid Build Coastguard Worker case NMethodType::kDeflate:
47*f6dc9357SAndroid Build Coastguard Worker _deflateDecoder = new NCompress::NDeflate::NDecoder::CCOMCoder();
48*f6dc9357SAndroid Build Coastguard Worker _codecInStream = _deflateDecoder;
49*f6dc9357SAndroid Build Coastguard Worker break;
50*f6dc9357SAndroid Build Coastguard Worker case NMethodType::kBZip2:
51*f6dc9357SAndroid Build Coastguard Worker _bzDecoder = new NCompress::NBZip2::CNsisDecoder();
52*f6dc9357SAndroid Build Coastguard Worker _codecInStream = _bzDecoder;
53*f6dc9357SAndroid Build Coastguard Worker break;
54*f6dc9357SAndroid Build Coastguard Worker case NMethodType::kLZMA:
55*f6dc9357SAndroid Build Coastguard Worker _lzmaDecoder = new NCompress::NLzma::CDecoder();
56*f6dc9357SAndroid Build Coastguard Worker _codecInStream = _lzmaDecoder;
57*f6dc9357SAndroid Build Coastguard Worker break;
58*f6dc9357SAndroid Build Coastguard Worker default: return E_NOTIMPL;
59*f6dc9357SAndroid Build Coastguard Worker }
60*f6dc9357SAndroid Build Coastguard Worker }
61*f6dc9357SAndroid Build Coastguard Worker
62*f6dc9357SAndroid Build Coastguard Worker if (Method == NMethodType::kDeflate)
63*f6dc9357SAndroid Build Coastguard Worker _deflateDecoder->SetNsisMode(IsNsisDeflate);
64*f6dc9357SAndroid Build Coastguard Worker
65*f6dc9357SAndroid Build Coastguard Worker if (FilterFlag)
66*f6dc9357SAndroid Build Coastguard Worker {
67*f6dc9357SAndroid Build Coastguard Worker Byte flag;
68*f6dc9357SAndroid Build Coastguard Worker RINOK(ReadStream_FALSE(inStream, &flag, 1))
69*f6dc9357SAndroid Build Coastguard Worker if (flag > 1)
70*f6dc9357SAndroid Build Coastguard Worker return E_NOTIMPL;
71*f6dc9357SAndroid Build Coastguard Worker useFilter = (flag != 0);
72*f6dc9357SAndroid Build Coastguard Worker }
73*f6dc9357SAndroid Build Coastguard Worker
74*f6dc9357SAndroid Build Coastguard Worker if (!useFilter)
75*f6dc9357SAndroid Build Coastguard Worker _decoderInStream = _codecInStream;
76*f6dc9357SAndroid Build Coastguard Worker else
77*f6dc9357SAndroid Build Coastguard Worker {
78*f6dc9357SAndroid Build Coastguard Worker if (!_filterInStream)
79*f6dc9357SAndroid Build Coastguard Worker {
80*f6dc9357SAndroid Build Coastguard Worker _filter = new CFilterCoder(false);
81*f6dc9357SAndroid Build Coastguard Worker _filterInStream = _filter;
82*f6dc9357SAndroid Build Coastguard Worker _filter->Filter = new NCompress::NBcj::CCoder2(z7_BranchConvSt_X86_Dec);
83*f6dc9357SAndroid Build Coastguard Worker }
84*f6dc9357SAndroid Build Coastguard Worker RINOK(_filter->SetInStream(_codecInStream))
85*f6dc9357SAndroid Build Coastguard Worker _decoderInStream = _filterInStream;
86*f6dc9357SAndroid Build Coastguard Worker }
87*f6dc9357SAndroid Build Coastguard Worker
88*f6dc9357SAndroid Build Coastguard Worker if (Method == NMethodType::kLZMA)
89*f6dc9357SAndroid Build Coastguard Worker {
90*f6dc9357SAndroid Build Coastguard Worker const unsigned kPropsSize = LZMA_PROPS_SIZE;
91*f6dc9357SAndroid Build Coastguard Worker Byte props[kPropsSize];
92*f6dc9357SAndroid Build Coastguard Worker RINOK(ReadStream_FALSE(inStream, props, kPropsSize))
93*f6dc9357SAndroid Build Coastguard Worker RINOK(_lzmaDecoder->SetDecoderProperties2((const Byte *)props, kPropsSize))
94*f6dc9357SAndroid Build Coastguard Worker }
95*f6dc9357SAndroid Build Coastguard Worker
96*f6dc9357SAndroid Build Coastguard Worker {
97*f6dc9357SAndroid Build Coastguard Worker CMyComPtr<ICompressSetInStream> setInStream;
98*f6dc9357SAndroid Build Coastguard Worker _codecInStream.QueryInterface(IID_ICompressSetInStream, &setInStream);
99*f6dc9357SAndroid Build Coastguard Worker if (!setInStream)
100*f6dc9357SAndroid Build Coastguard Worker return E_NOTIMPL;
101*f6dc9357SAndroid Build Coastguard Worker RINOK(setInStream->SetInStream(inStream))
102*f6dc9357SAndroid Build Coastguard Worker }
103*f6dc9357SAndroid Build Coastguard Worker
104*f6dc9357SAndroid Build Coastguard Worker {
105*f6dc9357SAndroid Build Coastguard Worker CMyComPtr<ICompressSetOutStreamSize> setOutStreamSize;
106*f6dc9357SAndroid Build Coastguard Worker _codecInStream.QueryInterface(IID_ICompressSetOutStreamSize, &setOutStreamSize);
107*f6dc9357SAndroid Build Coastguard Worker if (!setOutStreamSize)
108*f6dc9357SAndroid Build Coastguard Worker return E_NOTIMPL;
109*f6dc9357SAndroid Build Coastguard Worker RINOK(setOutStreamSize->SetOutStreamSize(NULL))
110*f6dc9357SAndroid Build Coastguard Worker }
111*f6dc9357SAndroid Build Coastguard Worker
112*f6dc9357SAndroid Build Coastguard Worker if (useFilter)
113*f6dc9357SAndroid Build Coastguard Worker {
114*f6dc9357SAndroid Build Coastguard Worker RINOK(_filter->SetOutStreamSize(NULL))
115*f6dc9357SAndroid Build Coastguard Worker }
116*f6dc9357SAndroid Build Coastguard Worker
117*f6dc9357SAndroid Build Coastguard Worker return S_OK;
118*f6dc9357SAndroid Build Coastguard Worker }
119*f6dc9357SAndroid Build Coastguard Worker
120*f6dc9357SAndroid Build Coastguard Worker
121*f6dc9357SAndroid Build Coastguard Worker static const UInt32 kMask_IsCompressed = (UInt32)1 << 31;
122*f6dc9357SAndroid Build Coastguard Worker
123*f6dc9357SAndroid Build Coastguard Worker
SetToPos(UInt64 pos,ICompressProgressInfo * progress)124*f6dc9357SAndroid Build Coastguard Worker HRESULT CDecoder::SetToPos(UInt64 pos, ICompressProgressInfo *progress)
125*f6dc9357SAndroid Build Coastguard Worker {
126*f6dc9357SAndroid Build Coastguard Worker if (StreamPos > pos)
127*f6dc9357SAndroid Build Coastguard Worker return E_FAIL;
128*f6dc9357SAndroid Build Coastguard Worker const UInt64 inSizeStart = GetInputProcessedSize();
129*f6dc9357SAndroid Build Coastguard Worker UInt64 offset = 0;
130*f6dc9357SAndroid Build Coastguard Worker while (StreamPos < pos)
131*f6dc9357SAndroid Build Coastguard Worker {
132*f6dc9357SAndroid Build Coastguard Worker size_t size = (size_t)MyMin(pos - StreamPos, (UInt64)Buffer.Size());
133*f6dc9357SAndroid Build Coastguard Worker RINOK(Read(Buffer, &size))
134*f6dc9357SAndroid Build Coastguard Worker if (size == 0)
135*f6dc9357SAndroid Build Coastguard Worker return S_FALSE;
136*f6dc9357SAndroid Build Coastguard Worker StreamPos += size;
137*f6dc9357SAndroid Build Coastguard Worker offset += size;
138*f6dc9357SAndroid Build Coastguard Worker
139*f6dc9357SAndroid Build Coastguard Worker const UInt64 inSize = GetInputProcessedSize() - inSizeStart;
140*f6dc9357SAndroid Build Coastguard Worker RINOK(progress->SetRatioInfo(&inSize, &offset))
141*f6dc9357SAndroid Build Coastguard Worker }
142*f6dc9357SAndroid Build Coastguard Worker return S_OK;
143*f6dc9357SAndroid Build Coastguard Worker }
144*f6dc9357SAndroid Build Coastguard Worker
145*f6dc9357SAndroid Build Coastguard Worker
Decode(CByteBuffer * outBuf,bool unpackSizeDefined,UInt32 unpackSize,ISequentialOutStream * realOutStream,ICompressProgressInfo * progress,UInt32 & packSizeRes,UInt32 & unpackSizeRes)146*f6dc9357SAndroid Build Coastguard Worker HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unpackSize,
147*f6dc9357SAndroid Build Coastguard Worker ISequentialOutStream *realOutStream, ICompressProgressInfo *progress,
148*f6dc9357SAndroid Build Coastguard Worker UInt32 &packSizeRes, UInt32 &unpackSizeRes)
149*f6dc9357SAndroid Build Coastguard Worker {
150*f6dc9357SAndroid Build Coastguard Worker CLimitedSequentialInStream *limitedStreamSpec = NULL;
151*f6dc9357SAndroid Build Coastguard Worker CMyComPtr<ISequentialInStream> limitedStream;
152*f6dc9357SAndroid Build Coastguard Worker packSizeRes = 0;
153*f6dc9357SAndroid Build Coastguard Worker unpackSizeRes = 0;
154*f6dc9357SAndroid Build Coastguard Worker
155*f6dc9357SAndroid Build Coastguard Worker if (Solid)
156*f6dc9357SAndroid Build Coastguard Worker {
157*f6dc9357SAndroid Build Coastguard Worker Byte temp[4];
158*f6dc9357SAndroid Build Coastguard Worker size_t processedSize = 4;
159*f6dc9357SAndroid Build Coastguard Worker RINOK(Read(temp, &processedSize))
160*f6dc9357SAndroid Build Coastguard Worker StreamPos += processedSize;
161*f6dc9357SAndroid Build Coastguard Worker if (processedSize != 4)
162*f6dc9357SAndroid Build Coastguard Worker return S_FALSE;
163*f6dc9357SAndroid Build Coastguard Worker UInt32 size = Get32(temp);
164*f6dc9357SAndroid Build Coastguard Worker if (unpackSizeDefined && size != unpackSize)
165*f6dc9357SAndroid Build Coastguard Worker return S_FALSE;
166*f6dc9357SAndroid Build Coastguard Worker unpackSize = size;
167*f6dc9357SAndroid Build Coastguard Worker unpackSizeDefined = true;
168*f6dc9357SAndroid Build Coastguard Worker }
169*f6dc9357SAndroid Build Coastguard Worker else
170*f6dc9357SAndroid Build Coastguard Worker {
171*f6dc9357SAndroid Build Coastguard Worker Byte temp[4];
172*f6dc9357SAndroid Build Coastguard Worker {
173*f6dc9357SAndroid Build Coastguard Worker size_t processedSize = 4;
174*f6dc9357SAndroid Build Coastguard Worker RINOK(ReadStream(InputStream, temp, &processedSize))
175*f6dc9357SAndroid Build Coastguard Worker StreamPos += processedSize;
176*f6dc9357SAndroid Build Coastguard Worker if (processedSize != 4)
177*f6dc9357SAndroid Build Coastguard Worker return S_FALSE;
178*f6dc9357SAndroid Build Coastguard Worker }
179*f6dc9357SAndroid Build Coastguard Worker UInt32 size = Get32(temp);
180*f6dc9357SAndroid Build Coastguard Worker
181*f6dc9357SAndroid Build Coastguard Worker if ((size & kMask_IsCompressed) == 0)
182*f6dc9357SAndroid Build Coastguard Worker {
183*f6dc9357SAndroid Build Coastguard Worker if (unpackSizeDefined && size != unpackSize)
184*f6dc9357SAndroid Build Coastguard Worker return S_FALSE;
185*f6dc9357SAndroid Build Coastguard Worker packSizeRes = size;
186*f6dc9357SAndroid Build Coastguard Worker if (outBuf)
187*f6dc9357SAndroid Build Coastguard Worker outBuf->Alloc(size);
188*f6dc9357SAndroid Build Coastguard Worker
189*f6dc9357SAndroid Build Coastguard Worker UInt64 offset = 0;
190*f6dc9357SAndroid Build Coastguard Worker
191*f6dc9357SAndroid Build Coastguard Worker while (size > 0)
192*f6dc9357SAndroid Build Coastguard Worker {
193*f6dc9357SAndroid Build Coastguard Worker UInt32 curSize = (UInt32)MyMin((size_t)size, Buffer.Size());
194*f6dc9357SAndroid Build Coastguard Worker UInt32 processedSize;
195*f6dc9357SAndroid Build Coastguard Worker RINOK(InputStream->Read(Buffer, curSize, &processedSize))
196*f6dc9357SAndroid Build Coastguard Worker if (processedSize == 0)
197*f6dc9357SAndroid Build Coastguard Worker return S_FALSE;
198*f6dc9357SAndroid Build Coastguard Worker if (outBuf)
199*f6dc9357SAndroid Build Coastguard Worker memcpy((Byte *)*outBuf + (size_t)offset, Buffer, processedSize);
200*f6dc9357SAndroid Build Coastguard Worker offset += processedSize;
201*f6dc9357SAndroid Build Coastguard Worker size -= processedSize;
202*f6dc9357SAndroid Build Coastguard Worker StreamPos += processedSize;
203*f6dc9357SAndroid Build Coastguard Worker unpackSizeRes += processedSize;
204*f6dc9357SAndroid Build Coastguard Worker if (realOutStream)
205*f6dc9357SAndroid Build Coastguard Worker RINOK(WriteStream(realOutStream, Buffer, processedSize))
206*f6dc9357SAndroid Build Coastguard Worker RINOK(progress->SetRatioInfo(&offset, &offset))
207*f6dc9357SAndroid Build Coastguard Worker }
208*f6dc9357SAndroid Build Coastguard Worker
209*f6dc9357SAndroid Build Coastguard Worker return S_OK;
210*f6dc9357SAndroid Build Coastguard Worker }
211*f6dc9357SAndroid Build Coastguard Worker
212*f6dc9357SAndroid Build Coastguard Worker size &= ~kMask_IsCompressed;
213*f6dc9357SAndroid Build Coastguard Worker packSizeRes = size;
214*f6dc9357SAndroid Build Coastguard Worker limitedStreamSpec = new CLimitedSequentialInStream;
215*f6dc9357SAndroid Build Coastguard Worker limitedStream = limitedStreamSpec;
216*f6dc9357SAndroid Build Coastguard Worker limitedStreamSpec->SetStream(InputStream);
217*f6dc9357SAndroid Build Coastguard Worker limitedStreamSpec->Init(size);
218*f6dc9357SAndroid Build Coastguard Worker {
219*f6dc9357SAndroid Build Coastguard Worker bool useFilter;
220*f6dc9357SAndroid Build Coastguard Worker RINOK(Init(limitedStream, useFilter))
221*f6dc9357SAndroid Build Coastguard Worker }
222*f6dc9357SAndroid Build Coastguard Worker }
223*f6dc9357SAndroid Build Coastguard Worker
224*f6dc9357SAndroid Build Coastguard Worker if (outBuf)
225*f6dc9357SAndroid Build Coastguard Worker {
226*f6dc9357SAndroid Build Coastguard Worker if (unpackSizeDefined)
227*f6dc9357SAndroid Build Coastguard Worker outBuf->Alloc(unpackSize);
228*f6dc9357SAndroid Build Coastguard Worker }
229*f6dc9357SAndroid Build Coastguard Worker
230*f6dc9357SAndroid Build Coastguard Worker const UInt64 inSizeStart = GetInputProcessedSize();
231*f6dc9357SAndroid Build Coastguard Worker
232*f6dc9357SAndroid Build Coastguard Worker // we don't allow files larger than 4 GB;
233*f6dc9357SAndroid Build Coastguard Worker if (!unpackSizeDefined)
234*f6dc9357SAndroid Build Coastguard Worker unpackSize = 0xFFFFFFFF;
235*f6dc9357SAndroid Build Coastguard Worker UInt32 offset = 0;
236*f6dc9357SAndroid Build Coastguard Worker
237*f6dc9357SAndroid Build Coastguard Worker HRESULT res = S_OK;
238*f6dc9357SAndroid Build Coastguard Worker
239*f6dc9357SAndroid Build Coastguard Worker for (;;)
240*f6dc9357SAndroid Build Coastguard Worker {
241*f6dc9357SAndroid Build Coastguard Worker size_t rem = unpackSize - offset;
242*f6dc9357SAndroid Build Coastguard Worker if (rem == 0)
243*f6dc9357SAndroid Build Coastguard Worker break;
244*f6dc9357SAndroid Build Coastguard Worker size_t size = Buffer.Size();
245*f6dc9357SAndroid Build Coastguard Worker if (size > rem)
246*f6dc9357SAndroid Build Coastguard Worker size = rem;
247*f6dc9357SAndroid Build Coastguard Worker RINOK(Read(Buffer, &size))
248*f6dc9357SAndroid Build Coastguard Worker if (size == 0)
249*f6dc9357SAndroid Build Coastguard Worker {
250*f6dc9357SAndroid Build Coastguard Worker if (unpackSizeDefined)
251*f6dc9357SAndroid Build Coastguard Worker res = S_FALSE;
252*f6dc9357SAndroid Build Coastguard Worker break;
253*f6dc9357SAndroid Build Coastguard Worker }
254*f6dc9357SAndroid Build Coastguard Worker
255*f6dc9357SAndroid Build Coastguard Worker if (outBuf)
256*f6dc9357SAndroid Build Coastguard Worker {
257*f6dc9357SAndroid Build Coastguard Worker size_t nextSize = offset + size;
258*f6dc9357SAndroid Build Coastguard Worker if (outBuf->Size() < nextSize)
259*f6dc9357SAndroid Build Coastguard Worker {
260*f6dc9357SAndroid Build Coastguard Worker {
261*f6dc9357SAndroid Build Coastguard Worker const size_t nextSize2 = outBuf->Size() * 2;
262*f6dc9357SAndroid Build Coastguard Worker if (nextSize < nextSize2)
263*f6dc9357SAndroid Build Coastguard Worker nextSize = nextSize2;
264*f6dc9357SAndroid Build Coastguard Worker }
265*f6dc9357SAndroid Build Coastguard Worker outBuf->ChangeSize_KeepData(nextSize, offset);
266*f6dc9357SAndroid Build Coastguard Worker }
267*f6dc9357SAndroid Build Coastguard Worker memcpy((Byte *)*outBuf + (size_t)offset, Buffer, size);
268*f6dc9357SAndroid Build Coastguard Worker }
269*f6dc9357SAndroid Build Coastguard Worker
270*f6dc9357SAndroid Build Coastguard Worker StreamPos += size;
271*f6dc9357SAndroid Build Coastguard Worker offset += (UInt32)size;
272*f6dc9357SAndroid Build Coastguard Worker
273*f6dc9357SAndroid Build Coastguard Worker const UInt64 inSize = GetInputProcessedSize() - inSizeStart;
274*f6dc9357SAndroid Build Coastguard Worker
275*f6dc9357SAndroid Build Coastguard Worker if (Solid)
276*f6dc9357SAndroid Build Coastguard Worker packSizeRes = (UInt32)inSize;
277*f6dc9357SAndroid Build Coastguard Worker unpackSizeRes += (UInt32)size;
278*f6dc9357SAndroid Build Coastguard Worker
279*f6dc9357SAndroid Build Coastguard Worker UInt64 outSize = offset;
280*f6dc9357SAndroid Build Coastguard Worker RINOK(progress->SetRatioInfo(&inSize, &outSize))
281*f6dc9357SAndroid Build Coastguard Worker if (realOutStream)
282*f6dc9357SAndroid Build Coastguard Worker {
283*f6dc9357SAndroid Build Coastguard Worker res = WriteStream(realOutStream, Buffer, size);
284*f6dc9357SAndroid Build Coastguard Worker if (res != S_OK)
285*f6dc9357SAndroid Build Coastguard Worker break;
286*f6dc9357SAndroid Build Coastguard Worker }
287*f6dc9357SAndroid Build Coastguard Worker }
288*f6dc9357SAndroid Build Coastguard Worker
289*f6dc9357SAndroid Build Coastguard Worker if (outBuf && offset != outBuf->Size())
290*f6dc9357SAndroid Build Coastguard Worker outBuf->ChangeSize_KeepData(offset, offset);
291*f6dc9357SAndroid Build Coastguard Worker
292*f6dc9357SAndroid Build Coastguard Worker return res;
293*f6dc9357SAndroid Build Coastguard Worker }
294*f6dc9357SAndroid Build Coastguard Worker
295*f6dc9357SAndroid Build Coastguard Worker }}
296