diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 138fef26..450e3a05 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -84,6 +84,7 @@ + @@ -544,7 +545,9 @@ - + + + perl "$(SolutionDir)inc-revision.pl" "$(ProjectPath)" $(ConfigurationName) diff --git a/ArcFormats/Entis/ArcNOA.cs b/ArcFormats/Entis/ArcNOA.cs index 344217de..77676437 100644 --- a/ArcFormats/Entis/ArcNOA.cs +++ b/ArcFormats/Entis/ArcNOA.cs @@ -123,56 +123,61 @@ namespace GameRes.Formats.Entis ulong size = arc.File.View.ReadUInt64 (entry.Offset+8); if (size > int.MaxValue) throw new FileSizeException(); - if (0 == size) + if (size <= 4) return Stream.Null; + if (EncType.ERISACode == nent.Encryption) + { + using (var enc = arc.File.CreateStream (entry.Offset+0x10, (uint)size-4)) + return DecodeNemesis (enc); + } + + var narc = arc as NoaArchive; var input = arc.File.CreateStream (entry.Offset+0x10, (uint)size); - try - { - var narc = arc as NoaArchive; - if (0 == nent.Encryption || size < 4 || null == narc || null == narc.Password) - return input; - if (0x40000000 != nent.Encryption) - { - Trace.WriteLine (string.Format ("{0}: unknown encryption scheme 0x{1:x8}", - nent.Name, nent.Encryption)); - return input; - } - uint nTotalBytes = (uint)(size - 4); - var pBSHF = new BSHFDecodeContext (0x10000); - pBSHF.AttachInputFile (input); - pBSHF.PrepareToDecodeBSHFCode (narc.Password); + if (EncType.Raw == nent.Encryption || null == narc || null == narc.Password) + return input; - byte[] buf = new byte[nTotalBytes]; - uint decoded = pBSHF.DecodeBSHFCodeBytes (buf, nTotalBytes); - if (decoded < nTotalBytes) - throw new EndOfStreamException ("Unexpected end of encrypted stream"); - - /* Something wrong with preceding length calculation, resulting CRC doesn't match - byte[] bufCRC = new byte[4]; - int iCRC = 0; - for (int i = 0; i < buf.Length; ++i) - { - bufCRC[iCRC] ^= buf[i]; - iCRC = (iCRC + 1) & 0x03; - } - uint orgCRC = arc.File.View.ReadUInt32 (entry.Offset+0x10+nTotalBytes); - uint crc = LittleEndian.ToUInt32 (bufCRC, 0); - if (orgCRC != crc) - { - Trace.WriteLine (string.Format ("{0}: CRC mismatch", nent.Name)); - input.Position = 0; - return input; - } - */ - input.Dispose(); - return new MemoryStream (buf); - } - catch + if (EncType.BSHFCrypt == nent.Encryption) { - input.Dispose(); - throw; + using (input) + return DecodeBSHF (input, narc.Password); } + Trace.WriteLine (string.Format ("{0}: encryption scheme 0x{1:x8} not implemented", + nent.Name, nent.Encryption)); + return input; + } + + Stream DecodeNemesis (Stream input) + { + var decoder = new NemesisDecodeContext(); + decoder.AttachInputFile (input); + decoder.PrepareToDecodeERISANCode(); + var file = new MemoryStream ((int)input.Length); + var buffer = new byte[0x10000]; + for (;;) + { + int read = (int)decoder.DecodeNemesisCodeBytes (buffer, 0x10000); + if (0 == read) + break; + file.Write (buffer, 0, read); + } + file.Position = 0; + return file; + } + + Stream DecodeBSHF (Stream input, string password) + { + uint nTotalBytes = (uint)input.Length - 4; + var pBSHF = new BSHFDecodeContext (0x10000); + pBSHF.AttachInputFile (input); + pBSHF.PrepareToDecodeBSHFCode (password); + + byte[] buf = new byte[nTotalBytes]; + uint decoded = pBSHF.DecodeBSHFCodeBytes (buf, nTotalBytes); + if (decoded < nTotalBytes) + throw new EndOfStreamException ("Unexpected end of encrypted stream"); + + return new MemoryStream (buf); } public override ResourceOptions GetDefaultOptions () @@ -188,6 +193,16 @@ namespace GameRes.Formats.Entis return new GUI.WidgetNOA(); } + internal static class EncType + { + public const uint Raw = 0x00000000; + public const uint ERISACode = 0x80000010; + public const uint BSHFCrypt = 0x40000000; + public const uint SimpleCrypt32 = 0x20000000; + public const uint ERISACrypt = 0xC0000010; + public const uint ERISACrypt32 = 0xA0000010; + } + internal class IndexReader { ArcView m_file; @@ -235,8 +250,7 @@ namespace GameRes.Formats.Entis dir_offset += 4; entry.Encryption = m_file.View.ReadUInt32 (dir_offset); - if (0 != entry.Encryption) - m_found_encrypted = true; + m_found_encrypted = m_found_encrypted || (EncType.Raw != entry.Encryption && EncType.ERISACode != entry.Encryption); dir_offset += 4; entry.Offset = base_offset + m_file.View.ReadInt64 (dir_offset); diff --git a/ArcFormats/Entis/EriReader.cs b/ArcFormats/Entis/EriReader.cs index f431a2cb..665f84d8 100644 --- a/ArcFormats/Entis/EriReader.cs +++ b/ArcFormats/Entis/EriReader.cs @@ -1368,16 +1368,16 @@ namespace GameRes.Formats.Entis internal class ProbDecodeContext : RLEDecodeContext { - uint m_dwCodeRegister; - uint m_dwAugendRegister; - int m_nPostBitCount; - byte[] m_bytLastSymbol = new byte[4]; + protected uint m_dwCodeRegister; + protected uint m_dwAugendRegister; + protected int m_nPostBitCount; + protected byte[] m_bytLastSymbol = new byte[4]; - ErisaProbModel m_pPhraseLenProb = new ErisaProbModel(); - ErisaProbModel m_pPhraseIndexProb = new ErisaProbModel(); - ErisaProbModel m_pRunLenProb = new ErisaProbModel(); - ErisaProbModel m_pLastERISAProb; - ErisaProbModel[] m_ppTableERISA; + protected ErisaProbModel m_pPhraseLenProb = new ErisaProbModel(); + protected ErisaProbModel m_pPhraseIndexProb = new ErisaProbModel(); + protected ErisaProbModel m_pRunLenProb = new ErisaProbModel(); + protected ErisaProbModel m_pLastERISAProb; + protected ErisaProbModel[] m_ppTableERISA; public ProbDecodeContext (uint nBufferingSize) : base (nBufferingSize) { @@ -1467,7 +1467,7 @@ namespace GameRes.Formats.Entis return nSymbol; } - int DecodeERISACodeIndex (ErisaProbModel pModel) + protected int DecodeERISACodeIndex (ErisaProbModel pModel) { uint dwAcc = m_dwCodeRegister * pModel.TotalCount / m_dwAugendRegister; if (dwAcc >= ErisaProbModel.TotalLimit) @@ -1499,7 +1499,7 @@ namespace GameRes.Formats.Entis { if ((++m_nPostBitCount) >= 256) return -1; - nNextBit = 0 ; + nNextBit = 0; } m_dwCodeRegister = (m_dwCodeRegister << 1) | ((uint)nNextBit & 1); m_dwAugendRegister <<= 1; diff --git a/ArcFormats/Entis/ErisaNemesis.cs b/ArcFormats/Entis/ErisaNemesis.cs new file mode 100644 index 00000000..539ce7db --- /dev/null +++ b/ArcFormats/Entis/ErisaNemesis.cs @@ -0,0 +1,293 @@ +//! \file ErisaNemesis.cs +//! \date Tue Jan 19 18:24:23 2016 +//! \brief Erisa Nemesis encoding implementation. +// + +using System; +using System.Diagnostics; + +namespace GameRes.Formats.Entis +{ + class NemesisDecodeContext : ProbDecodeContext + { + int m_iLastSymbol; + int m_nNemesisLeft; + int m_nNemesisNext; + byte[] m_pNemesisBuf; + int m_nNemesisIndex; + bool m_flagEOF; + + ErisaProbBase m_pProbERISA; + NemesisPhraseLookup[] m_pNemesisLookup; + + public NemesisDecodeContext (uint buffer_size = 0x10000) : base (buffer_size) + { + m_flagEOF = false; + } + + public void PrepareToDecodeERISANCode () + { + if (null == m_pProbERISA) + m_pProbERISA = new ErisaProbBase(); + + m_iLastSymbol = 0; + for (int i = 0; i < 4; ++i) + m_bytLastSymbol[i] = 0; + + m_pProbERISA.dwWorkUsed = 0; + m_pProbERISA.epmBaseModel.Initialize(); + + for (int i = 0; i < ErisaProbBase.SlotMax; ++i) + { + m_pProbERISA.ptrProbIndex[i] = new ErisaProbModel(); + } + PrepareToDecodeERISACode(); + + if (null == m_pNemesisBuf) + { + m_pNemesisBuf = new byte[Nemesis.BufSize]; + } + if (null == m_pNemesisLookup) + { + m_pNemesisLookup = new NemesisPhraseLookup[0x100]; + } + for (int i = 0; i < m_pNemesisBuf.Length; ++i) + m_pNemesisBuf[i] = 0; + for (int i = 0; i < m_pNemesisLookup.Length; ++i) + m_pNemesisLookup[i] = new NemesisPhraseLookup(); + m_nNemesisIndex = 0; + + m_nNemesisLeft = 0; + m_flagEOF = false; + } + + public override uint DecodeBytes (Array ptrDst, uint nCount) + { + return DecodeNemesisCodeBytes (ptrDst as byte[], nCount); + } + + public uint DecodeNemesisCodeBytes (byte[] ptrDst, uint nCount) + { + if (m_flagEOF) + return 0; + + ErisaProbBase pBase = m_pProbERISA; + uint nDecoded = 0; + int dst = 0; + byte bytSymbol; + while (nDecoded < nCount) + { + if (m_nNemesisLeft > 0) + { + uint nNemesisCount = (uint)m_nNemesisLeft; + if (nNemesisCount > nCount - nDecoded) + { + nNemesisCount = nCount - nDecoded; + } + byte bytLastSymbol = m_pNemesisBuf[(m_nNemesisIndex - 1) & Nemesis.BufMask]; + + for (uint i = 0; i < nNemesisCount; ++i) + { + bytSymbol = bytLastSymbol; + if (m_nNemesisNext >= 0) + { + bytSymbol = m_pNemesisBuf[m_nNemesisNext++]; + m_nNemesisNext &= Nemesis.BufMask; + } + m_bytLastSymbol[m_iLastSymbol++] = bytSymbol; + m_iLastSymbol &= 3; + + var phrase = m_pNemesisLookup[bytSymbol]; + phrase.index[phrase.first] = (uint)m_nNemesisIndex; + phrase.first = (phrase.first + 1) & Nemesis.IndexMask; + bytLastSymbol = bytSymbol; + + m_pNemesisBuf[m_nNemesisIndex++] = bytSymbol; + m_nNemesisIndex &= Nemesis.BufMask; + + ptrDst[dst++] = bytSymbol; + } + m_nNemesisLeft -= (int)nNemesisCount; + nDecoded += nNemesisCount; + continue; + } + + int iDeg; + ErisaProbModel pModel = pBase.epmBaseModel; + for (iDeg = 0; iDeg < 4; ++iDeg) + { + int iLast = m_bytLastSymbol[(m_iLastSymbol + 3 - iDeg) & 3] + >> ErisaProbBase.m_nShiftCount[iDeg]; + if (pModel.SubModel[iLast].Symbol < 0) + { + break; + } + Debug.Assert ((uint)pModel.SubModel[iLast].Symbol < pBase.dwWorkUsed); + pModel = pBase.ptrProbIndex[pModel.SubModel[iLast].Symbol]; + } + int iSym = DecodeERISACodeIndex (pModel); + if (iSym < 0) + { + return nDecoded; + } + int nSymbol = pModel.SymTable[iSym].Symbol; + int iSymIndex = pModel.IncreaseSymbol (iSym); + + bool fNemesis = false; + if (nSymbol == ErisaProbModel.EscCode) + { + if (pModel != pBase.epmBaseModel) + { + iSym = DecodeERISACodeIndex (pBase.epmBaseModel); + if (iSym < 0) + { + return nDecoded; + } + nSymbol = pBase.epmBaseModel.SymTable[iSym].Symbol; + pBase.epmBaseModel.IncreaseSymbol (iSym); + if (nSymbol != ErisaProbModel.EscCode) + { + pModel.AddSymbol ((short)nSymbol); + iSym = -1; + } + else + { + fNemesis = true; + } + } + else + { + fNemesis = true; + } + } + if (fNemesis) + { + int nLength, nPhraseIndex; + nPhraseIndex = DecodeERISACode (m_pPhraseIndexProb); + if (nPhraseIndex == ErisaProbModel.EscCode) + { + m_flagEOF = true; + return nDecoded; + } + if (0 == nPhraseIndex) + { + nLength = DecodeERISACode (m_pRunLenProb); + } + else + { + nLength = DecodeERISACode (m_pPhraseLenProb); + } + if (nLength == ErisaProbModel.EscCode) + { + return nDecoded; + } + byte bytLastSymbol = m_pNemesisBuf[(m_nNemesisIndex - 1) & Nemesis.BufMask]; + var phrase = m_pNemesisLookup[bytLastSymbol]; + m_nNemesisLeft = nLength; + if (0 == nPhraseIndex) + { + m_nNemesisNext = -1; + } + else + { + m_nNemesisNext = (int)phrase.index[(phrase.first - nPhraseIndex) & Nemesis.IndexMask]; + Debug.Assert (m_pNemesisBuf[m_nNemesisNext] == bytLastSymbol); + m_nNemesisNext = (m_nNemesisNext + 1) & Nemesis.BufMask; + } + continue; + } + bytSymbol = (byte)nSymbol; + m_bytLastSymbol[m_iLastSymbol++] = bytSymbol; + m_iLastSymbol &= 3; + + var ppl = m_pNemesisLookup[bytSymbol]; + ppl.index[ppl.first] = (uint)m_nNemesisIndex; + ppl.first = (ppl.first + 1) & Nemesis.IndexMask; + m_pNemesisBuf[m_nNemesisIndex++] = bytSymbol; + m_nNemesisIndex &= Nemesis.BufMask; + + ptrDst[dst++] = bytSymbol; + nDecoded++; + + if ((pBase.dwWorkUsed < ErisaProbBase.SlotMax) && (iDeg < 4)) + { + int iSymbol = ((byte)nSymbol) >> ErisaProbBase.m_nShiftCount[iDeg]; + Debug.Assert (iSymbol < ErisaProbModel.SubSortMax); + if (++pModel.SubModel[iSymbol].Occured >= ErisaProbBase.m_nNewProbLimit[iDeg]) + { + int i; + ErisaProbModel pParent = pModel; + pModel = pBase.epmBaseModel; + for (i = 0; i <= iDeg; ++i) + { + iSymbol = m_bytLastSymbol[(m_iLastSymbol + 3 - i) & 3] + >> ErisaProbBase.m_nShiftCount[i]; + if (pModel.SubModel[iSymbol].Symbol < 0) + { + break; + } + Debug.Assert ((uint)pModel.SubModel[iSymbol].Symbol < pBase.dwWorkUsed); + pModel = pBase.ptrProbIndex[pModel.SubModel[iSymbol].Symbol]; + } + if ((i <= iDeg) && (pModel.SubModel[iSymbol].Symbol < 0)) + { + ErisaProbModel pNew = pBase.ptrProbIndex[pBase.dwWorkUsed]; + pModel.SubModel[iSymbol].Symbol = (short)(pBase.dwWorkUsed++); + + pNew.TotalCount = 0; + int j = 0; + for (i = 0; i < (int)pParent.SymbolSorts; ++i) + { + ushort wOccured = (ushort)(pParent.SymTable[i].Occured >> 4); + if (wOccured > 0 && (pParent.SymTable[i].Symbol != ErisaProbModel.EscCode)) + { + pNew.TotalCount += wOccured; + pNew.SymTable[j].Occured = wOccured; + pNew.SymTable[j].Symbol = pParent.SymTable[i].Symbol; + j++; + } + } + pNew.TotalCount++; + pNew.SymTable[j].Occured = 1; + pNew.SymTable[j].Symbol = ErisaProbModel.EscCode; + pNew.SymbolSorts = ++j; + + for (i = 0; i < ErisaProbModel.SubSortMax; ++i) + { + pNew.SubModel[i].Occured = 0; + pNew.SubModel[i].Symbol = -1; + } + } + } + } + } + return nDecoded; + } + } + + internal class ErisaProbBase + { + public uint dwWorkUsed; + public ErisaProbModel epmBaseModel = new ErisaProbModel(); + public ErisaProbModel[] ptrProbIndex = new ErisaProbModel[SlotMax]; + + public const int SlotMax = 0x800; + + public static readonly int[] m_nShiftCount = { 1, 3, 4, 5 }; + public static readonly int[] m_nNewProbLimit = { 0x01, 0x08, 0x10, 0x20 }; + } + + internal static class Nemesis + { + public const int BufSize = 0x10000; + public const int BufMask = 0xFFFF; + public const int IndexLimit = 0x100; + public const int IndexMask = 0xFF; + } + + internal class NemesisPhraseLookup + { + public uint first; + public uint[] index = new uint[Nemesis.IndexLimit]; + } +}