/* PpmdHandler.c -- PPMd format handler 2010-03-10 : Igor Pavlov : Public domain This code is based on: PPMd var.H (2001) / var.I (2002): Dmitry Shkarin : Public domain Carryless rangecoder (1999): Dmitry Subbotin : Public domain */ #include "StdAfx.h" #include "../../../C/CpuArch.h" #include "../../../C/Alloc.h" #include "../../../C/Ppmd7.h" #include "../../../C/Ppmd8.h" #include "Common/ComTry.h" #include "Common/IntToString.h" #include "Common/StringConvert.h" #include "Windows/PropVariant.h" #include "Windows/Time.h" #include "../Common/CWrappers.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" using namespace NWindows; namespace NArchive { namespace NPpmd { static void *SzBigAlloc(void *, size_t size) { return BigAlloc(size); } static void SzBigFree(void *, void *address) { BigFree(address); } static ISzAlloc g_BigAlloc = { SzBigAlloc, SzBigFree }; static const UInt32 kBufSize = (1 << 20); struct CBuf { Byte *Buf; CBuf(): Buf(0) {} ~CBuf() { ::MidFree(Buf); } bool Alloc() { if (!Buf) Buf = (Byte *)::MidAlloc(kBufSize); return (Buf != 0); } }; static const UInt32 kHeaderSize = 16; static const UInt32 kSignature = 0x84ACAF8F; static const unsigned kNewHeaderVer = 8; struct CItem { UInt32 Attrib; UInt32 Time; AString Name; unsigned Order; unsigned MemInMB; unsigned Ver; unsigned Restor; HRESULT ReadHeader(ISequentialInStream *s, UInt32 &headerSize); bool IsSupported() const { return Ver == 7 || (Ver == 8 && Restor <= 1); } }; HRESULT CItem::ReadHeader(ISequentialInStream *s, UInt32 &headerSize) { Byte h[kHeaderSize]; RINOK(ReadStream_FALSE(s, h, kHeaderSize)); if (GetUi32(h) != kSignature) return S_FALSE; Attrib = GetUi32(h + 4); Time = GetUi32(h + 12); unsigned info = GetUi16(h + 8); Order = (info & 0xF) + 1; MemInMB = ((info >> 4) & 0xFF) + 1; Ver = info >> 12; UInt32 nameLen = GetUi16(h + 10); Restor = nameLen >> 14; if (Restor > 2) return S_FALSE; if (Ver >= kNewHeaderVer) nameLen &= 0x3FFF; if (nameLen > (1 << 9)) return S_FALSE; char *name = Name.GetBuffer(nameLen + 1); HRESULT res = ReadStream_FALSE(s, name, nameLen); name[nameLen] = 0; headerSize = kHeaderSize + nameLen; Name.ReleaseBuffer(); return res; } class CHandler: public IInArchive, public IArchiveOpenSeq, public CMyUnknownImp { CItem _item; UInt32 _headerSize; UInt64 _packSize; bool _packSizeDefined; CMyComPtr _stream; public: MY_UNKNOWN_IMP2(IInArchive, IArchiveOpenSeq) INTERFACE_IInArchive(;) STDMETHOD(OpenSeq)(ISequentialInStream *stream); }; STATPROPSTG kProps[] = { { NULL, kpidPath, VT_BSTR}, { NULL, kpidMTime, VT_FILETIME}, { NULL, kpidAttrib, VT_UI4}, { NULL, kpidMethod, VT_BSTR} }; IMP_IInArchive_Props IMP_IInArchive_ArcProps_NO_Table STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { NCOM::CPropVariant prop; switch(propID) { case kpidPhySize: if (_packSizeDefined) prop = _packSize; break; } prop.Detach(value); return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = 1; return S_OK; } static void UIntToString(AString &s, const char *prefix, unsigned value) { s += prefix; char temp[16]; ::ConvertUInt32ToString((UInt32)value, temp); s += temp; } STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; switch(propID) { case kpidPath: prop = MultiByteToUnicodeString(_item.Name, CP_ACP); break; case kpidMTime: { FILETIME utc; if (NTime::DosTimeToFileTime(_item.Time, utc)) prop = utc; break; } case kpidAttrib: prop = _item.Attrib; break; case kpidPackSize: if (_packSizeDefined) prop = _packSize; break; case kpidMethod: { AString s = "PPMd"; s += (char)('A' + _item.Ver); UIntToString(s, ":o", _item.Order); UIntToString(s, ":mem", _item.MemInMB); s += 'm'; if (_item.Ver >= kNewHeaderVer && _item.Restor != 0) UIntToString(s, ":r", _item.Restor); prop = s; } } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *) { return OpenSeq(stream); } STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) { COM_TRY_BEGIN HRESULT res; try { Close(); res = _item.ReadHeader(stream, _headerSize); } catch(...) { res = S_FALSE; } if (res == S_OK) _stream = stream; else Close(); return res; COM_TRY_END } STDMETHODIMP CHandler::Close() { _packSizeDefined = false; _stream.Release(); return S_OK; } static const UInt32 kTopValue = (1 << 24); static const UInt32 kBot = (1 << 15); struct CRangeDecoder { IPpmd7_RangeDec s; UInt32 Range; UInt32 Code; UInt32 Low; CByteInBufWrap *Stream; public: bool Init() { Code = 0; Low = 0; Range = 0xFFFFFFFF; for (int i = 0; i < 4; i++) Code = (Code << 8) | Stream->ReadByte(); return Code < 0xFFFFFFFF; } void Normalize() { while ((Low ^ (Low + Range)) < kTopValue || Range < kBot && ((Range = (0 - Low) & (kBot - 1)), 1)) { Code = (Code << 8) | Stream->ReadByte(); Range <<= 8; Low <<= 8; } } CRangeDecoder(); }; extern "C" { static UInt32 Range_GetThreshold(void *pp, UInt32 total) { CRangeDecoder *p = (CRangeDecoder *)pp; return p->Code / (p->Range /= total); } static void Range_Decode(void *pp, UInt32 start, UInt32 size) { CRangeDecoder *p = (CRangeDecoder *)pp; start *= p->Range; p->Low += start; p->Code -= start; p->Range *= size; p->Normalize(); } static UInt32 Range_DecodeBit(void *pp, UInt32 size0) { CRangeDecoder *p = (CRangeDecoder *)pp; if (p->Code / (p->Range >>= 14) < size0) { Range_Decode(p, 0, size0); return 0; } else { Range_Decode(p, size0, (1 << 14) - size0); return 1; } } } CRangeDecoder::CRangeDecoder() { s.GetThreshold = Range_GetThreshold; s.Decode = Range_Decode; s.DecodeBit = Range_DecodeBit; } struct CPpmdCpp { unsigned Ver; CRangeDecoder _rc; CPpmd7 _ppmd7; CPpmd8 _ppmd8; CPpmdCpp(unsigned version) { Ver = version; Ppmd7_Construct(&_ppmd7); Ppmd8_Construct(&_ppmd8); } ~CPpmdCpp() { Ppmd7_Free(&_ppmd7, &g_BigAlloc); Ppmd8_Free(&_ppmd8, &g_BigAlloc); } bool Alloc(UInt32 memInMB) { memInMB <<= 20; if (Ver == 7) return Ppmd7_Alloc(&_ppmd7, memInMB, &g_BigAlloc) != 0; return Ppmd8_Alloc(&_ppmd8, memInMB, &g_BigAlloc) != 0; } void Init(unsigned order, unsigned restor) { if (Ver == 7) Ppmd7_Init(&_ppmd7, order); else Ppmd8_Init(&_ppmd8, order, restor);; } bool InitRc(CByteInBufWrap *inStream) { if (Ver == 7) { _rc.Stream = inStream; return _rc.Init(); } else { _ppmd8.Stream.In = &inStream->p; return Ppmd8_RangeDec_Init(&_ppmd8) != 0; } } bool IsFinishedOK() { if (Ver == 7) return Ppmd7z_RangeDec_IsFinishedOK(&_rc); return Ppmd8_RangeDec_IsFinishedOK(&_ppmd8); } }; STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { if (numItems == 0) return S_OK; if (numItems != (UInt32)-1 && (numItems != 1 || indices[0] != 0)) return E_INVALIDARG; // extractCallback->SetTotal(_packSize); UInt64 currentTotalPacked = 0; RINOK(extractCallback->SetCompleted(¤tTotalPacked)); CMyComPtr realOutStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); if (!testMode && !realOutStream) return S_OK; extractCallback->PrepareOperation(askMode); CByteInBufWrap inBuf; if (!inBuf.Alloc(1 << 20)) return E_OUTOFMEMORY; inBuf.Stream = _stream; CBuf outBuf; if (!outBuf.Alloc()) return E_OUTOFMEMORY; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, true); CPpmdCpp ppmd(_item.Ver); if (!ppmd.Alloc(_item.MemInMB)) return E_OUTOFMEMORY; Int32 opRes = NExtract::NOperationResult::kUnSupportedMethod; if (_item.IsSupported()) { opRes = NExtract::NOperationResult::kDataError; ppmd.Init(_item.Order, _item.Restor); inBuf.Init(); UInt64 outSize = 0; if (ppmd.InitRc(&inBuf) && !inBuf.Extra && inBuf.Res == S_OK) for (;;) { lps->InSize = _packSize = inBuf.GetProcessed(); lps->OutSize = outSize; RINOK(lps->SetCur()); size_t i; int sym = 0; if (ppmd.Ver == 7) { for (i = 0; i < kBufSize; i++) { sym = Ppmd7_DecodeSymbol(&ppmd._ppmd7, &ppmd._rc.s); if (inBuf.Extra || sym < 0) break; outBuf.Buf[i] = (Byte)sym; } } else { for (i = 0; i < kBufSize; i++) { sym = Ppmd8_DecodeSymbol(&ppmd._ppmd8); if (inBuf.Extra || sym < 0) break; outBuf.Buf[i] = (Byte)sym; } } outSize += i; _packSize = _headerSize + inBuf.GetProcessed(); _packSizeDefined = true; if (realOutStream) { RINOK(WriteStream(realOutStream, outBuf.Buf, i)); } if (sym < 0) { if (sym == -1 && ppmd.IsFinishedOK()) opRes = NExtract::NOperationResult::kOK; break; } } RINOK(inBuf.Res); } realOutStream.Release(); return extractCallback->SetOperationResult(opRes); } static IInArchive *CreateArc() { return new CHandler; } static CArcInfo g_ArcInfo = { L"Ppmd", L"pmd", 0, 0xD, { 0x8F, 0xAF, 0xAC, 0x84 }, 4, false, CreateArc, 0 }; REGISTER_ARC(Ppmd) }}