2023-08-24 01:33:50 +04:00
//! \file ArcDX8.cs
//! \date 2022 Jun 05
//! \brief DxLib archive version 8.
//
// Copyright (C) 2022 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
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
2024-07-17 19:53:13 +02:00
using GameRes.Formats.Strings ;
2023-08-24 01:33:50 +04:00
using System ;
2024-08-11 18:59:30 +02:00
using System.CodeDom ;
2023-08-24 01:33:50 +04:00
using System.Collections.Generic ;
using System.ComponentModel.Composition ;
using System.IO ;
2024-07-17 19:53:13 +02:00
using System.Linq ;
2024-08-11 18:59:30 +02:00
using System.Runtime.Remoting.Messaging ;
2024-08-01 11:46:41 +02:00
using System.Windows.Navigation ;
2024-08-11 18:59:30 +02:00
using static GameRes . Formats . DxLib . Dx8Opener ;
2024-07-17 19:53:13 +02:00
2023-08-24 01:33:50 +04:00
namespace GameRes.Formats.DxLib
{
2024-08-01 11:46:41 +02:00
internal class DXA8PackedEntry : PackedEntry {
public bool HuffmanCompressed { get ; set ; }
2024-08-02 19:29:19 +02:00
public uint HuffmanSize { get ; set ; }
2024-08-11 18:59:30 +02:00
public uint LZSize { get ; set ; }
}
internal class DxArchive8 : DxArchive
{
public byte huffmanMaxKB ;
public DxArchive8 ( ArcView arc , ArchiveFormat impl , ICollection < Entry > dir , IDxKey enc , int version , byte huffmanKB ) : base ( arc , impl , dir , enc , version )
{
huffmanMaxKB = huffmanKB ;
}
2024-08-01 11:46:41 +02:00
}
2024-08-11 18:59:30 +02:00
internal class DxHeaderV8 : DxHeader
{
public DXA8Flags Flags ;
public byte HuffmanKB ; // oddly used only in Compression process not in decompression.
//15 bytes of padding.
}
2024-08-01 14:42:40 +02:00
2024-08-11 18:59:30 +02:00
[Export(typeof(ArchiveFormat))]
2023-08-24 01:33:50 +04:00
public class Dx8Opener : DxOpener
{
public override string Tag { get { return "BIN/DXLIB" ; } }
public override string Description { get { return "DxLib archive version 8" ; } }
public override uint Signature { get { return 0x00085844 ; } }
public override bool IsHierarchic { get { return true ; } }
public override bool CanWrite { get { return false ; } }
public Dx8Opener ( )
{
2024-07-15 14:15:14 +02:00
Extensions = new string [ ] { "dxa" , "hud" , "usi" , "med" , "dat" , "bin" , "bcx" , "wolf" } ;
2023-08-24 01:33:50 +04:00
Signatures = new [ ] { 0x00085844 u } ;
}
2024-08-02 19:29:19 +02:00
//static readonly byte[] DefaultKey = new byte[] { 0xBE, 0xC8, 0x8A, 0xF5, 0x28, 0x50, 0xC9 };
2023-08-24 01:33:50 +04:00
2024-07-15 14:15:14 +02:00
DxScheme DefaultScheme = new DxScheme { KnownKeys = new List < IDxKey > ( ) } ;
2024-08-11 18:59:30 +02:00
2024-07-15 14:15:14 +02:00
internal enum DXA8Flags : UInt32
{
DXA_FLAG_NO_KEY = 1 , //file is not encrypted
2024-07-31 16:32:53 +02:00
DXA_FLAG_NO_HEAD_PRESS = 1 < < 1 , //do not compress the header after compressing individual entries
2024-07-17 19:53:13 +02:00
}
[Serializable]
public class DXAOpts : ResourceOptions
{
public string Keyword ;
}
public override ResourceOptions GetDefaultOptions ( )
{
return new DXAOpts
{
Keyword = Properties . Settings . Default . DXAPassword
} ;
}
2024-08-02 19:29:19 +02:00
2024-07-17 19:53:13 +02:00
public override ResourceOptions GetOptions ( object widget )
{
if ( widget is GUI . WidgetDXA )
{
return new DXAOpts
{
Keyword = ( ( GUI . WidgetDXA ) widget ) . Password . Text
} ;
}
return GetDefaultOptions ( ) ;
}
public override object GetAccessWidget ( )
{
return new GUI . WidgetDXA ( ) ;
2024-07-15 14:15:14 +02:00
}
2023-08-24 01:33:50 +04:00
public override ArcFile TryOpen ( ArcView file )
{
2024-07-15 14:15:14 +02:00
var dx = new DxHeaderV8 {
2023-08-24 01:33:50 +04:00
IndexSize = file . View . ReadUInt32 ( 4 ) ,
BaseOffset = file . View . ReadInt64 ( 8 ) ,
IndexOffset = file . View . ReadInt64 ( 0x10 ) ,
2024-07-15 14:15:14 +02:00
FileTable = file . View . ReadInt64 ( 0x18 ) ,
DirTable = file . View . ReadInt64 ( 0x20 ) ,
2023-08-24 01:33:50 +04:00
CodePage = file . View . ReadInt32 ( 0x28 ) ,
2024-07-15 14:15:14 +02:00
Flags = ( DXA8Flags ) file . View . ReadUInt32 ( 0x2C ) ,
HuffmanKB = file . View . ReadByte ( 0x30 )
2023-08-24 01:33:50 +04:00
} ;
if ( dx . DirTable > = dx . IndexSize | | dx . FileTable > = dx . IndexSize )
return null ;
2024-07-17 19:53:13 +02:00
DxKey8 key = null ;
//FIXME: ReadBytes sets hard cap of filesize to 4GB.
2024-07-31 16:32:53 +02:00
var headerBuffer = file . View . ReadBytes ( dx . IndexOffset , ( uint ) ( file . MaxOffset - dx . IndexOffset ) ) ;
2024-07-17 19:53:13 +02:00
bool isencrypted = ( dx . Flags & DXA8Flags . DXA_FLAG_NO_KEY ) = = 0 ;
if ( isencrypted )
{
var keyStr = Query < DXAOpts > ( arcStrings . ZIPEncryptedNotice ) . Keyword ;
2024-08-18 10:46:41 +02:00
key = new DxKey8 ( keyStr , dx . CodePage ) ;
2024-07-17 19:53:13 +02:00
}
2024-07-31 16:32:53 +02:00
Decrypt ( headerBuffer , 0 , headerBuffer . Length , 0 , key . Key ) ;
2024-07-17 19:53:13 +02:00
//Decrypted but might be compressed
if ( ( dx . Flags & DXA8Flags . DXA_FLAG_NO_HEAD_PRESS ) = = 0 )
{
2024-08-05 15:07:56 +02:00
byte [ ] huffmanBuffer = new byte [ headerBuffer . Length ] ;
byte [ ] lzBuffer ;
headerBuffer . CopyTo ( huffmanBuffer , 0 ) ;
2024-08-05 15:17:26 +02:00
//huffmanBuffer = headerBuffer;
2024-08-05 15:07:56 +02:00
HuffmanDecoder decoder = new HuffmanDecoder ( huffmanBuffer , ( ulong ) huffmanBuffer . LongLength ) ;
lzBuffer = decoder . Unpack ( ) ;
MemoryStream lzStream = new MemoryStream ( lzBuffer ) ;
headerBuffer = Unpack ( lzStream ) ;
2024-07-17 19:53:13 +02:00
}
2024-07-31 16:32:53 +02:00
2024-07-17 19:53:13 +02:00
List < Entry > entries ;
2024-08-11 18:59:30 +02:00
//There MAY be the case where the singular file is over 4GB, but it's very rare.
using ( var reader = IndexReader . Create ( dx , 8 , new MemoryStream ( headerBuffer ) ) )
{
entries = reader . Read ( ) ;
}
return new DxArchive8 ( file , this , entries , key , 8 , dx . HuffmanKB ) ;
2024-08-14 18:30:45 +02:00
//retu rn null;
2024-08-11 18:59:30 +02:00
}
public override Stream OpenEntry ( ArcFile arc , Entry entry )
{
Stream input = arc . File . CreateStream ( entry . Offset , entry . Size ) ;
var dx_arc = arc as DxArchive8 ;
if ( null = = dx_arc )
return input ;
var dx_ent = ( DXA8PackedEntry ) entry ;
2024-08-14 18:30:45 +02:00
long dec_offset = dx_ent . UnpackedSize ;
2024-08-11 18:59:30 +02:00
var key = dx_arc . Encryption . GetEntryKey ( dx_ent . Name ) ;
input = new EncryptedStream ( input , dec_offset , key ) ;
byte [ ] tmpBuffer = new byte [ dx_ent . Size ] ;
input . Read ( tmpBuffer , 0 , tmpBuffer . Length ) ;
if ( dx_ent . HuffmanCompressed )
{
byte [ ] buffer = new byte [ dx_ent . HuffmanSize ] ;
byte [ ] outBuffer = new byte [ dx_ent . IsPacked ? dx_ent . LZSize : dx_ent . UnpackedSize ] ;
Array . Copy ( tmpBuffer , buffer , dx_ent . HuffmanSize ) ;
HuffmanDecoder decoder = new HuffmanDecoder ( buffer , dx_ent . HuffmanSize ) ;
byte [ ] partTmpBuffer = decoder . Unpack ( ) ;
//returned buffer might be partial. Check if this is the case.
var outBufSize = dx_ent . IsPacked ? dx_ent . LZSize : dx_ent . UnpackedSize ;
if ( dx_arc . huffmanMaxKB ! = 0xff & & outBufSize > dx_arc . huffmanMaxKB * 1024 * 2 )
{
//What we have here is two huffmanMaxKB KB buffers, that constitute the beginning and end of file respectively.
Array . Copy ( partTmpBuffer , 0 , outBuffer , 0 , dx_arc . huffmanMaxKB * 1024 ) ;
Array . Copy ( partTmpBuffer , dx_arc . huffmanMaxKB * 1024 , outBuffer , outBuffer . Length - dx_arc . huffmanMaxKB * 1024 , dx_arc . huffmanMaxKB * 1024 ) ;
//uncompressed part goes into middle.
2024-08-14 18:30:45 +02:00
Array . Copy ( tmpBuffer , dx_ent . HuffmanSize , outBuffer , dx_arc . huffmanMaxKB * 1024 , outBufSize - dx_arc . huffmanMaxKB * 1024 * 2 ) ;
2024-08-11 18:59:30 +02:00
tmpBuffer = outBuffer ;
} else
{
//that is all that needs to be done.
tmpBuffer = partTmpBuffer ;
}
}
if ( dx_ent . IsPacked )
{
byte [ ] buffer = new byte [ dx_ent . LZSize ] ;
tmpBuffer . CopyTo ( buffer , 0 ) ;
var tmpMemStream = new MemoryStream ( buffer ) ;
tmpBuffer = Unpack ( tmpMemStream ) ;
}
return new BinMemoryStream ( tmpBuffer , entry . Name ) ;
/ *
if ( ! dx_ent . IsPacked )
return input ;
using ( input )
2024-07-17 19:53:13 +02:00
{
2024-08-11 18:59:30 +02:00
var data = Unpack ( input ) ;
return new BinMemoryStream ( data , entry . Name ) ;
2024-07-17 19:53:13 +02:00
}
2024-08-11 18:59:30 +02:00
* /
2024-07-17 19:53:13 +02:00
//return null;
}
}
internal sealed class IndexReaderV8 : IndexReader
{
readonly int m_entry_size ;
public IndexReaderV8 ( DxHeader header , int version , Stream input ) : base ( header , version , input )
{
m_entry_size = 0x48 ;
}
private class DxDirectory
{
public long DirOffset ;
public long ParentDirOffset ;
public int FileCount ;
public long FileTable ;
}
2024-07-15 14:15:14 +02:00
2024-07-17 19:53:13 +02:00
DxDirectory ReadDirEntry ( )
{
2024-08-02 19:29:19 +02:00
var dir = new DxDirectory
{
DirOffset = m_input . ReadInt64 ( ) ,
ParentDirOffset = m_input . ReadInt64 ( ) ,
FileCount = ( int ) m_input . ReadInt64 ( ) ,
FileTable = m_input . ReadInt64 ( )
} ;
2024-07-17 19:53:13 +02:00
return dir ;
}
2024-07-15 14:22:34 +02:00
2024-07-17 19:53:13 +02:00
protected override void ReadFileTable ( string root , long table_offset )
{
m_input . Position = m_header . DirTable + table_offset ;
var dir = ReadDirEntry ( ) ;
if ( dir . DirOffset ! = - 1 & & dir . ParentDirOffset ! = - 1 )
{
m_input . Position = m_header . FileTable + dir . DirOffset ;
root = Path . Combine ( root , ExtractFileName ( m_input . ReadInt64 ( ) ) ) ;
}
long current_pos = m_header . FileTable + dir . FileTable ;
for ( int i = 0 ; i < dir . FileCount ; + + i )
2024-07-15 14:15:14 +02:00
{
2024-07-17 19:53:13 +02:00
m_input . Position = current_pos ;
var name_offset = m_input . ReadInt64 ( ) ;
uint attr = ( uint ) m_input . ReadInt64 ( ) ;
m_input . Seek ( 0x18 , SeekOrigin . Current ) ;
var offset = m_input . ReadInt64 ( ) ;
if ( 0 ! = ( attr & 0x10 ) ) // FILE_ATTRIBUTE_DIRECTORY
2024-07-15 14:20:17 +02:00
{
2024-07-17 19:53:13 +02:00
if ( 0 = = offset | | table_offset = = offset )
throw new InvalidFormatException ( "Infinite recursion in DXA directory index" ) ;
ReadFileTable ( root , offset ) ;
2024-07-15 14:20:17 +02:00
}
else
{
2024-07-17 19:53:13 +02:00
var size = m_input . ReadInt64 ( ) ;
var packed_size = m_input . ReadInt64 ( ) ;
var huffman_packed_size = m_input . ReadInt64 ( ) ;
2024-08-01 11:46:41 +02:00
var entry = FormatCatalog . Instance . Create < DXA8PackedEntry > ( Path . Combine ( root , ExtractFileName ( name_offset ) ) ) ;
2024-07-17 19:53:13 +02:00
entry . Offset = m_header . BaseOffset + offset ;
entry . UnpackedSize = ( uint ) size ;
2024-08-01 11:46:41 +02:00
entry . IsPacked = - 1 ! = packed_size ;
entry . HuffmanCompressed = - 1 ! = huffman_packed_size ;
2024-08-02 19:29:19 +02:00
entry . HuffmanSize = ( uint ) huffman_packed_size ;
2024-08-11 18:59:30 +02:00
entry . LZSize = ( uint ) packed_size ;
//Huffman compression: huffman_packed_size will not exceed 2*HuffmanKB KB. The rest of data is uncompressed (as far as Huffman compressor is concerned).
//Add length of uncompressed data to entry.Size
if ( entry . HuffmanCompressed )
{
var outBufSize = entry . IsPacked ? packed_size : size ;
var dx8_hdr = ( DxHeaderV8 ) m_header ;
if ( outBufSize > dx8_hdr . HuffmanKB * 1024 * 2 )
{
huffman_packed_size + = outBufSize - dx8_hdr . HuffmanKB * 1024 * 2 ;
}
}
if ( entry . IsPacked | | entry . HuffmanCompressed )
2024-07-17 19:53:13 +02:00
entry . Size = ( uint ) ( huffman_packed_size ! = - 1 ? huffman_packed_size : packed_size ) ;
else
entry . Size = ( uint ) size ;
m_dir . Add ( entry ) ;
2024-07-15 14:20:17 +02:00
}
2024-07-17 19:53:13 +02:00
current_pos + = m_entry_size ;
2024-07-15 14:15:14 +02:00
}
2023-08-24 01:33:50 +04:00
}
}
}