From e1771edc819f2e0c3df5ccbac36e3ae5e3f48a40 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 7 Sep 2018 15:47:10 +0400 Subject: [PATCH] (Entis): improved ErisaN compression handling. --- ArcFormats/Entis/ArcNOA.cs | 45 +++++++++------------------- ArcFormats/Entis/ErisaNemesis.cs | 50 ++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/ArcFormats/Entis/ArcNOA.cs b/ArcFormats/Entis/ArcNOA.cs index 4b162457..4c197b3d 100644 --- a/ArcFormats/Entis/ArcNOA.cs +++ b/ArcFormats/Entis/ArcNOA.cs @@ -2,7 +2,7 @@ //! \date Thu Apr 23 15:57:17 2015 //! \brief Entis GLS engine archives implementation. // -// Copyright (C) 2015-2016 by morkt +// Copyright (C) 2015-2018 by morkt // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -77,7 +77,7 @@ namespace GameRes.Formats.Entis public NoaOpener () { - Extensions = new string[] { "noa", "dat", "rsa" }; + Extensions = new string[] { "noa", "dat", "rsa", "arc" }; Settings = new[] { NoaEncoding }; } @@ -96,13 +96,13 @@ namespace GameRes.Formats.Entis var reader = new IndexReader (file, NoaEncoding.Get()); if (!reader.ParseRoot() || 0 == reader.Dir.Count) return null; - if (!reader.HasEncrypted) - return new ArcFile (file, this, reader.Dir); - - var password = GetArcPassword (file.Name); - if (string.IsNullOrEmpty (password)) - return new ArcFile (file, this, reader.Dir); - return new NoaArchive (file, this, reader.Dir, password); + if (reader.HasEncrypted) + { + var password = GetArcPassword (file.Name); + if (!string.IsNullOrEmpty (password)) + return new NoaArchive (file, this, reader.Dir, password); + } + return new ArcFile (file, this, reader.Dir); } public override Stream OpenEntry (ArcFile arc, Entry entry) @@ -118,8 +118,8 @@ namespace GameRes.Formats.Entis if (EncType.ERISACode == nent.Encryption) { - using (var enc = arc.File.CreateStream (entry.Offset+0x10, (uint)size-4)) - return DecodeNemesis (enc); + var enc = arc.File.CreateStream (entry.Offset+0x10, (uint)size-4); + return new ErisaNemesisStream (enc, (int)entry.Size); } var narc = arc as NoaArchive; @@ -183,7 +183,7 @@ namespace GameRes.Formats.Entis if (null == cotomi) continue; using (var res = new MemoryStream (cotomi)) - using (var input = DecodeNemesis (res)) + using (var input = new ErisaNemesisStream (res)) { var xml = new XmlDocument(); xml.Load (input); @@ -210,24 +210,6 @@ namespace GameRes.Formats.Entis return null; } - 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; @@ -317,10 +299,11 @@ namespace GameRes.Formats.Entis entry.Encryption = m_file.View.ReadUInt32 (dir_offset); m_found_encrypted = m_found_encrypted || (EncType.Raw != entry.Encryption && EncType.ERISACode != entry.Encryption); + bool is_packed = EncType.ERISACode == entry.Encryption; dir_offset += 4; entry.Offset = base_offset + m_file.View.ReadInt64 (dir_offset); - if (!entry.CheckPlacement (m_file.MaxOffset)) + if (!is_packed && !entry.CheckPlacement (m_file.MaxOffset)) { entry.Size = (uint)(m_file.MaxOffset - entry.Offset); } diff --git a/ArcFormats/Entis/ErisaNemesis.cs b/ArcFormats/Entis/ErisaNemesis.cs index b8fe8700..1e0c20b6 100644 --- a/ArcFormats/Entis/ErisaNemesis.cs +++ b/ArcFormats/Entis/ErisaNemesis.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; +using System.IO; namespace GameRes.Formats.Entis { @@ -64,17 +65,16 @@ namespace GameRes.Formats.Entis public override uint DecodeBytes (Array ptrDst, uint nCount) { - return DecodeNemesisCodeBytes (ptrDst as byte[], nCount); + return DecodeNemesisCodeBytes (ptrDst as byte[], 0, nCount); } - public uint DecodeNemesisCodeBytes (byte[] ptrDst, uint nCount) + public uint DecodeNemesisCodeBytes (byte[] ptrDst, int dst, uint nCount) { if (m_flagEOF) return 0; ErisaProbBase pBase = m_pProbERISA; uint nDecoded = 0; - int dst = 0; byte bytSymbol; while (nDecoded < nCount) { @@ -150,7 +150,6 @@ namespace GameRes.Formats.Entis if (nSymbol != ErisaProbModel.EscCode) { pModel.AddSymbol ((short)nSymbol); - iSym = -1; } else { @@ -214,7 +213,7 @@ namespace GameRes.Formats.Entis if ((pBase.dwWorkUsed < ErisaProbBase.SlotMax) && (iDeg < 4)) { - int iSymbol = ((byte)nSymbol) >> ErisaProbBase.m_nShiftCount[iDeg]; + int iSymbol = bytSymbol >> ErisaProbBase.m_nShiftCount[iDeg]; if (iSymbol >= ErisaProbModel.SubSortMax) throw new InvalidFormatException ("Invalid Nemesis encoding sequence"); if (++pModel.SubModel[iSymbol].Occured >= ErisaProbBase.m_nNewProbLimit[iDeg]) @@ -295,4 +294,45 @@ namespace GameRes.Formats.Entis public uint first; public uint[] index = new uint[Nemesis.IndexLimit]; } + + internal class ErisaNemesisStream : InputProxyStream + { + NemesisDecodeContext m_decoder; + int m_remaining; + bool m_eof = false; + + public ErisaNemesisStream (Stream input, int output_length = 0) : base (input) + { + m_decoder = new NemesisDecodeContext(); + m_decoder.AttachInputFile (input); + m_decoder.PrepareToDecodeERISANCode(); + m_remaining = output_length > 0 ? output_length : int.MaxValue; + } + + public override int Read (byte[] buffer, int offset, int count) + { + int read = 0; + if (!m_eof && m_remaining > 0) + { + uint chunk_size = (uint)Math.Min (count, m_remaining); + read = (int)m_decoder.DecodeNemesisCodeBytes (buffer, offset, chunk_size); + m_eof = read < count; + m_remaining -= read; + } + return read; + } + + public override bool CanSeek { get { return false; } } + public override long Length { get { throw new NotSupportedException(); } } + + public override long Position { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + } }