mirror of
https://github.com/crskycode/GARbro.git
synced 2026-07-08 01:30:18 +08:00
reorganized project directory structure.
This commit is contained in:
548
ArcFormats/Entis/ArcNOA.cs
Normal file
548
ArcFormats/Entis/ArcNOA.cs
Normal file
@@ -0,0 +1,548 @@
|
||||
//! \file ArcNOA.cs
|
||||
//! \date Thu Apr 23 15:57:17 2015
|
||||
//! \brief Entis GLS engine archives implementation.
|
||||
//
|
||||
// Copyright (C) 2015 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.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using GameRes.Formats.Properties;
|
||||
using GameRes.Formats.Strings;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Entis
|
||||
{
|
||||
internal class NoaOptions : ResourceOptions
|
||||
{
|
||||
public string Scheme { get; set; }
|
||||
public string PassPhrase { get; set; }
|
||||
}
|
||||
|
||||
internal class NoaEntry : Entry
|
||||
{
|
||||
public byte[] Extra;
|
||||
public uint Encryption;
|
||||
public uint Attr;
|
||||
}
|
||||
|
||||
internal class NoaArchive : ArcFile
|
||||
{
|
||||
public string Password;
|
||||
|
||||
public NoaArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, string password = null)
|
||||
: base (arc, impl, dir)
|
||||
{
|
||||
Password = password;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class NoaOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "NOA"; } }
|
||||
public override string Description { get { return "Entis GLS engine resource archive"; } }
|
||||
public override uint Signature { get { return 0x69746e45; } } // 'Enti'
|
||||
public override bool IsHierarchic { get { return true; } }
|
||||
public override bool CanCreate { get { return false; } }
|
||||
|
||||
public NoaOpener ()
|
||||
{
|
||||
Extensions = new string[] { "noa", "dat" };
|
||||
}
|
||||
|
||||
public static readonly Dictionary<string, Dictionary<string, string>> KnownKeys =
|
||||
new Dictionary<string, Dictionary<string, string>> {
|
||||
{ arcStrings.NOAIgnoreEncryption, new Dictionary<string, string>() },
|
||||
{ "Alea Akaki Tsuki o Haruka ni Nozomi", new Dictionary<string, string> {
|
||||
{ "data2.noa", "pnnAiYVqktMdLlVq9pnrXs1795vhu8ZluLh3MxmXyBBrhrhLoP2rlGn5dxcBP6d1cAAz08TMRIXNUFatVdJFWAwVphtAh4hx5NHMmLs8LoBE2KHAA8GnKJB1PpKeyMHu" },
|
||||
{ "data4.noa", "yEbgydEFtIq3YiGUNMpCarJwR9mZbufPrbXtsoqbrJwT4F278kOWIgYzLtm1nP1Hns81u3F4Stwc42gdtrWIbnp9XfX3LsKiZe1TFUyrlTqsbhX8R8dEAVxLk9SVvCE7" } } },
|
||||
{ "Do S Ane to Boku no Hounyou Kankei", new Dictionary<string, string> {
|
||||
{ "d02.dat", "vwerc7s65r21bnfu" },
|
||||
{ "d03.dat", "ctfvgbhnj67y8u" } } },
|
||||
{ "Innyuu Famiresu", new Dictionary<string, string> {
|
||||
{ "d01.dat", "vdiu$43AfUCfh9aksf" },
|
||||
{ "d03.dat", "gaivnwq7365e021gf" } } },
|
||||
{ "Konneko", new Dictionary<string, string> {
|
||||
{ "script.noa", "convini_cat" } } },
|
||||
{ "Yatohime Zankikou", new Dictionary<string, string> {
|
||||
{ "data1.noa", "arcdatapass" },
|
||||
{ "data6.noa", "cfe7231hf9qccda" },
|
||||
{ "data7.noa", "ceiuvw86680efq0hHDUHF673j" } } },
|
||||
{ "You! Apron Chakuyou", new Dictionary<string, string> {
|
||||
{ "containerb.noa", "7DQ1Xm7ZahIv1ZwlFgyMTMryKC6OP9V6cAgL64WD5JLyvmeEyqTSA5rUbRigOtebnnK4MuOptwsbOf4K8UBDH4kpAUOQgB71Qr1qxtHGxQl8KZKj6WIYWpPh0G3JOJat" } } },
|
||||
};
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
if (!file.View.AsciiEqual (0, "Entis\x1a"))
|
||||
return null;
|
||||
uint id = file.View.ReadUInt32 (8);
|
||||
if (0x02000400 != id)
|
||||
return null;
|
||||
var reader = new IndexReader (file);
|
||||
if (!reader.ParseRoot() || 0 == reader.Dir.Count)
|
||||
return null;
|
||||
if (!reader.HasEncrypted)
|
||||
return new ArcFile (file, this, reader.Dir);
|
||||
|
||||
var options = Query<NoaOptions> (arcStrings.ArcEncryptedNotice);
|
||||
string password = null;
|
||||
if (!string.IsNullOrEmpty (options.PassPhrase))
|
||||
{
|
||||
password = options.PassPhrase;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty (options.Scheme))
|
||||
{
|
||||
Dictionary<string, string> filemap;
|
||||
if (KnownKeys.TryGetValue (options.Scheme, out filemap))
|
||||
{
|
||||
var filename = Path.GetFileName (file.Name).ToLowerInvariant();
|
||||
filemap.TryGetValue (filename, out password);
|
||||
}
|
||||
}
|
||||
if (string.IsNullOrEmpty (password))
|
||||
return new ArcFile (file, this, reader.Dir);
|
||||
return new NoaArchive (file, this, reader.Dir, password);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
var nent = entry as NoaEntry;
|
||||
if (null == nent || !arc.File.View.AsciiEqual (entry.Offset, "filedata"))
|
||||
return arc.File.CreateStream (entry.Offset, entry.Size);
|
||||
ulong size = arc.File.View.ReadUInt64 (entry.Offset+8);
|
||||
if (size > int.MaxValue)
|
||||
throw new FileSizeException();
|
||||
if (0 == size)
|
||||
return Stream.Null;
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
{
|
||||
input.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override ResourceOptions GetDefaultOptions ()
|
||||
{
|
||||
return new NoaOptions {
|
||||
Scheme = Settings.Default.NOAScheme,
|
||||
PassPhrase = Settings.Default.NOAPassPhrase,
|
||||
};
|
||||
}
|
||||
|
||||
public override object GetAccessWidget ()
|
||||
{
|
||||
return new GUI.WidgetNOA();
|
||||
}
|
||||
|
||||
internal class IndexReader
|
||||
{
|
||||
ArcView m_file;
|
||||
List<Entry> m_dir = new List<Entry>();
|
||||
bool m_found_encrypted = false;
|
||||
|
||||
const char PathSeparatorChar = '/';
|
||||
|
||||
public List<Entry> Dir { get { return m_dir; } }
|
||||
|
||||
public bool HasEncrypted { get { return m_found_encrypted; } }
|
||||
|
||||
public IndexReader (ArcView file)
|
||||
{
|
||||
m_file = file;
|
||||
}
|
||||
|
||||
public bool ParseRoot ()
|
||||
{
|
||||
return ParseDirEntry (0x40, "");
|
||||
}
|
||||
|
||||
private bool ParseDirEntry (long dir_offset, string cur_dir)
|
||||
{
|
||||
if (!m_file.View.AsciiEqual (dir_offset, "DirEntry"))
|
||||
return false;
|
||||
long size = m_file.View.ReadInt64 (dir_offset+8);
|
||||
if (size <= 0 || size > int.MaxValue)
|
||||
return false;
|
||||
if ((uint)size > m_file.View.Reserve (dir_offset+8, (uint)size))
|
||||
return false;
|
||||
long base_offset = dir_offset;
|
||||
dir_offset += 0x10;
|
||||
int count = m_file.View.ReadInt32 (dir_offset);
|
||||
dir_offset += 4;
|
||||
if (m_dir.Capacity < m_dir.Count+count)
|
||||
m_dir.Capacity = m_dir.Count+count;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
var entry = new NoaEntry();
|
||||
entry.Size = m_file.View.ReadUInt32 (dir_offset);
|
||||
dir_offset += 8;
|
||||
|
||||
entry.Attr = m_file.View.ReadUInt32 (dir_offset);
|
||||
dir_offset += 4;
|
||||
|
||||
entry.Encryption = m_file.View.ReadUInt32 (dir_offset);
|
||||
if (0 != entry.Encryption)
|
||||
m_found_encrypted = true;
|
||||
dir_offset += 4;
|
||||
|
||||
entry.Offset = base_offset + m_file.View.ReadInt64 (dir_offset);
|
||||
if (!entry.CheckPlacement (m_file.MaxOffset))
|
||||
{
|
||||
entry.Size = (uint)(m_file.MaxOffset - entry.Offset);
|
||||
}
|
||||
dir_offset += 0x10;
|
||||
|
||||
uint extra_length = m_file.View.ReadUInt32 (dir_offset);
|
||||
dir_offset += 4;
|
||||
if (extra_length > 0 && 0 == (entry.Attr & 0x70))
|
||||
{
|
||||
entry.Extra = new byte[extra_length];
|
||||
if (entry.Extra.Length != m_file.View.Read (dir_offset, entry.Extra, 0, extra_length))
|
||||
return false;
|
||||
}
|
||||
dir_offset += extra_length;
|
||||
uint name_length = m_file.View.ReadUInt32 (dir_offset);
|
||||
dir_offset += 4;
|
||||
|
||||
string name = m_file.View.ReadString (dir_offset, name_length);
|
||||
dir_offset += name_length;
|
||||
|
||||
if (string.IsNullOrEmpty (cur_dir))
|
||||
entry.Name = name;
|
||||
else
|
||||
entry.Name = cur_dir + PathSeparatorChar + name;
|
||||
entry.Type = FormatCatalog.Instance.GetTypeFromName (name);
|
||||
if (0x10 == entry.Attr)
|
||||
{
|
||||
if (!ParseDirEntry (entry.Offset+0x10, entry.Name))
|
||||
return false;
|
||||
}
|
||||
else if (0x20 == entry.Attr || 0x40 == entry.Attr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dir.Add (entry);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class ERISADecodeContext
|
||||
{
|
||||
protected int m_nIntBufCount;
|
||||
protected uint m_dwIntBuffer;
|
||||
protected uint m_nBufferingSize;
|
||||
protected uint m_nBufCount;
|
||||
protected byte[] m_ptrBuffer;
|
||||
protected int m_ptrNextBuf;
|
||||
|
||||
protected Stream m_pFile;
|
||||
protected ERISADecodeContext m_pContext;
|
||||
|
||||
public ERISADecodeContext (uint nBufferingSize)
|
||||
{
|
||||
m_nIntBufCount = 0;
|
||||
m_nBufferingSize = (nBufferingSize + 0x03) & ~0x03u;
|
||||
m_nBufCount = 0;
|
||||
m_ptrBuffer = new byte[nBufferingSize];
|
||||
m_pFile = null;
|
||||
m_pContext = null;
|
||||
}
|
||||
|
||||
public void AttachInputFile (Stream file)
|
||||
{
|
||||
m_pFile = file;
|
||||
m_pContext = null;
|
||||
}
|
||||
|
||||
public void AttachInputContext (ERISADecodeContext context)
|
||||
{
|
||||
m_pFile = null;
|
||||
m_pContext = context;
|
||||
}
|
||||
|
||||
public uint ReadNextData (byte[] ptrBuffer, uint nBytes)
|
||||
{
|
||||
if (m_pFile != null)
|
||||
{
|
||||
return (uint)m_pFile.Read (ptrBuffer, 0, (int)nBytes);
|
||||
}
|
||||
else if (m_pContext != null)
|
||||
{
|
||||
return m_pContext.DecodeBytes (ptrBuffer, nBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ApplicationException ("Uninitialized ERISA encryption context");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract uint DecodeBytes (Array ptrDst, uint nCount);
|
||||
|
||||
protected bool PrefetchBuffer()
|
||||
{
|
||||
if (0 == m_nIntBufCount)
|
||||
{
|
||||
if (0 == m_nBufCount)
|
||||
{
|
||||
m_ptrNextBuf = 0; // m_ptrBuffer;
|
||||
m_nBufCount = ReadNextData (m_ptrBuffer, m_nBufferingSize);
|
||||
if (0 == m_nBufCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (0 != (m_nBufCount & 0x03))
|
||||
{
|
||||
uint i = m_nBufCount;
|
||||
m_nBufCount += 4 - (m_nBufCount & 0x03);
|
||||
while (i < m_nBufCount)
|
||||
m_ptrBuffer[i ++] = 0;
|
||||
}
|
||||
}
|
||||
m_nIntBufCount = 32;
|
||||
m_dwIntBuffer =
|
||||
((uint)m_ptrBuffer[m_ptrNextBuf] << 24) | ((uint)m_ptrBuffer[m_ptrNextBuf+1] << 16)
|
||||
| ((uint)m_ptrBuffer[m_ptrNextBuf+2] << 8) | (uint)m_ptrBuffer[m_ptrNextBuf+3];
|
||||
m_ptrNextBuf += 4;
|
||||
m_nBufCount -= 4;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void FlushBuffer ()
|
||||
{
|
||||
m_nIntBufCount = 0;
|
||||
m_nBufCount = 0;
|
||||
}
|
||||
|
||||
public int GetABit ()
|
||||
{
|
||||
if (!PrefetchBuffer())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
int nValue = ((int)m_dwIntBuffer) >> 31;
|
||||
--m_nIntBufCount;
|
||||
m_dwIntBuffer <<= 1;
|
||||
return nValue;
|
||||
}
|
||||
|
||||
public uint GetNBits (int n)
|
||||
{
|
||||
uint nCode = 0;
|
||||
while (n != 0)
|
||||
{
|
||||
if (!PrefetchBuffer())
|
||||
break;
|
||||
|
||||
int nCopyBits = Math.Min (n, m_nIntBufCount);
|
||||
nCode = (nCode << nCopyBits) | (m_dwIntBuffer >> (32 - nCopyBits));
|
||||
n -= nCopyBits;
|
||||
m_nIntBufCount -= nCopyBits;
|
||||
m_dwIntBuffer <<= nCopyBits;
|
||||
}
|
||||
return nCode;
|
||||
}
|
||||
}
|
||||
|
||||
internal class BSHFDecodeContext : ERISADecodeContext
|
||||
{
|
||||
ERIBshfBuffer m_pBshfBuf;
|
||||
uint m_dwBufPos;
|
||||
|
||||
public BSHFDecodeContext (uint nBufferingSize) : base (nBufferingSize)
|
||||
{
|
||||
m_pBshfBuf = null;
|
||||
}
|
||||
|
||||
public void PrepareToDecodeBSHFCode (string pszPassword)
|
||||
{
|
||||
if (null == m_pBshfBuf)
|
||||
{
|
||||
m_pBshfBuf = new ERIBshfBuffer();
|
||||
}
|
||||
if (string.IsNullOrEmpty (pszPassword))
|
||||
{
|
||||
pszPassword = " ";
|
||||
}
|
||||
int char_count = Encoding.ASCII.GetByteCount (pszPassword);
|
||||
int length = Math.Max (char_count, 32);
|
||||
var pass_bytes = new byte[length];
|
||||
char_count = Encoding.ASCII.GetBytes (pszPassword, 0, pszPassword.Length, pass_bytes, 0);
|
||||
if (char_count < 32)
|
||||
{
|
||||
pass_bytes[char_count++] = 0x1b;
|
||||
for (int i = char_count; i < 32; ++i)
|
||||
{
|
||||
pass_bytes[i] = (byte)(pass_bytes[i % char_count] + pass_bytes[i - 1]);
|
||||
}
|
||||
}
|
||||
m_pBshfBuf.m_strPassword = pass_bytes;
|
||||
m_pBshfBuf.m_dwPassOffset = 0;
|
||||
m_dwBufPos = 32;
|
||||
}
|
||||
|
||||
public override uint DecodeBytes (Array ptrDst, uint nCount)
|
||||
{
|
||||
return DecodeBSHFCodeBytes (ptrDst as byte[], nCount);
|
||||
}
|
||||
|
||||
public uint DecodeBSHFCodeBytes (byte[] ptrDst, uint nCount)
|
||||
{
|
||||
uint nDecoded = 0;
|
||||
while (nDecoded < nCount)
|
||||
{
|
||||
if (m_dwBufPos >= 32)
|
||||
{
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
if (0 == m_nBufCount)
|
||||
{
|
||||
m_ptrNextBuf = 0;
|
||||
m_nBufCount = ReadNextData (m_ptrBuffer, m_nBufferingSize);
|
||||
if (0 == m_nBufCount)
|
||||
{
|
||||
return nDecoded;
|
||||
}
|
||||
}
|
||||
m_pBshfBuf.m_srcBSHF[i] = m_ptrBuffer[m_ptrNextBuf++];
|
||||
m_nBufCount--;
|
||||
}
|
||||
m_pBshfBuf.DecodeBuffer();
|
||||
m_dwBufPos = 0;
|
||||
}
|
||||
ptrDst[nDecoded++] = m_pBshfBuf.m_bufBSHF[m_dwBufPos++];
|
||||
}
|
||||
return nDecoded;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ERIBshfBuffer
|
||||
{
|
||||
public byte[] m_strPassword;
|
||||
public uint m_dwPassOffset = 0;
|
||||
public byte[] m_bufBSHF = new byte[32];
|
||||
public byte[] m_srcBSHF = new byte[32];
|
||||
public byte[] m_maskBSHF = new byte[32];
|
||||
|
||||
public void DecodeBuffer ()
|
||||
{
|
||||
int nPassLen = m_strPassword.Length;
|
||||
if ((int)m_dwPassOffset >= nPassLen)
|
||||
{
|
||||
m_dwPassOffset = 0;
|
||||
}
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
m_bufBSHF[i] = 0;
|
||||
m_maskBSHF[i] = 0;
|
||||
}
|
||||
int iPos = (int) m_dwPassOffset++;
|
||||
int iBit = 0;
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
iBit = (iBit + m_strPassword[iPos++]) & 0xFF;
|
||||
if (iPos >= nPassLen)
|
||||
{
|
||||
iPos = 0;
|
||||
}
|
||||
int iOffset = (iBit >> 3);
|
||||
int iMask = (0x80 >> (iBit & 0x07));
|
||||
while (0xFF == m_maskBSHF[iOffset])
|
||||
{
|
||||
iBit = (iBit + 8) & 0xFF;
|
||||
iOffset = (iBit >> 3);
|
||||
}
|
||||
while (0 != (m_maskBSHF[iOffset] & iMask))
|
||||
{
|
||||
iBit ++;
|
||||
iMask >>= 1;
|
||||
if (0 == iMask)
|
||||
{
|
||||
iBit = (iBit + 8) & 0xFF;
|
||||
iOffset = (iBit >> 3);
|
||||
iMask = 0x80;
|
||||
}
|
||||
}
|
||||
Debug.Assert (iMask != 0);
|
||||
m_maskBSHF[iOffset] |= (byte) iMask;
|
||||
|
||||
if (0 != (m_srcBSHF[(i >> 3)] & (0x80 >> (i & 0x07))))
|
||||
{
|
||||
m_bufBSHF[iOffset] |= (byte)iMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
387
ArcFormats/Entis/AudioMIO.cs
Normal file
387
ArcFormats/Entis/AudioMIO.cs
Normal file
@@ -0,0 +1,387 @@
|
||||
//! \file AudioMIO.cs
|
||||
//! \date Thu May 28 13:33:07 2015
|
||||
//! \brief Entis audio format implementation.
|
||||
//
|
||||
// Copyright (C) 2015 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.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Entis
|
||||
{
|
||||
[Export(typeof(AudioFormat))]
|
||||
public class MioAudio : AudioFormat
|
||||
{
|
||||
public override string Tag { get { return "MIO"; } }
|
||||
public override string Description { get { return "Entis engine compressed audio format"; } }
|
||||
public override uint Signature { get { return 0x69746e45u; } } // 'Enti'
|
||||
|
||||
public override SoundInput TryOpen (Stream file)
|
||||
{
|
||||
byte[] header = new byte[0x40];
|
||||
if (header.Length != file.Read (header, 0, header.Length))
|
||||
return null;
|
||||
if (0x03000100 != LittleEndian.ToUInt32 (header, 8))
|
||||
return null;
|
||||
if (!Binary.AsciiEqual (header, 0x10, "Music Interleaved and Orthogonal transformed"))
|
||||
return null;
|
||||
|
||||
return new MioInput (file);
|
||||
}
|
||||
}
|
||||
|
||||
public class MioInput : SoundInput
|
||||
{
|
||||
MioInfoHeader m_info;
|
||||
long m_stream_pos;
|
||||
int m_bitrate;
|
||||
uint m_total_samples;
|
||||
ERISADecodeContext m_pmioc;
|
||||
MioDecoder m_pmiod;
|
||||
Stream m_decoded_stream;
|
||||
|
||||
public int ChannelCount { get { return m_info.ChannelCount; } }
|
||||
public uint BitsPerSample { get { return m_info.BitsPerSample; } }
|
||||
|
||||
public override int SourceBitrate { get { return m_bitrate; } }
|
||||
public override string SourceFormat { get { return "mio"; } }
|
||||
|
||||
#region Stream Members
|
||||
public override bool CanSeek { get { return m_decoded_stream.CanSeek; } }
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { return m_decoded_stream.Position; }
|
||||
set { m_decoded_stream.Position = value; }
|
||||
}
|
||||
|
||||
public override int Read (byte[] buffer, int offset, int count)
|
||||
{
|
||||
return Read_Threaded (buffer, offset, count);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public MioInput (Stream file) : base (file)
|
||||
{
|
||||
file.Position = 0x40;
|
||||
using (var erif = new EriFile (file))
|
||||
{
|
||||
var section = erif.ReadSection();
|
||||
if (section.Id != "Header " || section.Length <= 0 || section.Length > int.MaxValue)
|
||||
throw new InvalidFormatException();
|
||||
m_stream_pos = 0x50 + section.Length;
|
||||
int header_size = (int)section.Length;
|
||||
while (header_size > 8)
|
||||
{
|
||||
section = erif.ReadSection();
|
||||
header_size -= 8;
|
||||
if (section.Length <= 0 || section.Length > header_size)
|
||||
break;
|
||||
if ("SoundInf" == section.Id)
|
||||
{
|
||||
m_info = new MioInfoHeader();
|
||||
m_info.Version = erif.ReadInt32();
|
||||
m_info.Transformation = (CvType)erif.ReadInt32();
|
||||
m_info.Architecture = (EriCode)erif.ReadInt32();
|
||||
m_info.ChannelCount = erif.ReadInt32();
|
||||
m_info.SamplesPerSec = erif.ReadUInt32();
|
||||
m_info.BlocksetCount = erif.ReadUInt32();
|
||||
m_info.SubbandDegree = erif.ReadInt32();
|
||||
m_info.AllSampleCount = erif.ReadUInt32();
|
||||
m_info.LappedDegree = erif.ReadUInt32();
|
||||
m_info.BitsPerSample = erif.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
header_size -= (int)section.Length;
|
||||
erif.BaseStream.Seek (section.Length, SeekOrigin.Current);
|
||||
}
|
||||
if (null == m_info)
|
||||
throw new InvalidFormatException ("MIO sound header not found");
|
||||
|
||||
erif.BaseStream.Position = m_stream_pos;
|
||||
var stream_size = erif.FindSection ("Stream ");
|
||||
m_stream_pos = erif.BaseStream.Position;
|
||||
|
||||
m_pmiod = new MioDecoder (m_info);
|
||||
if (EriCode.Nemesis != m_info.Architecture)
|
||||
m_pmioc = new HuffmanDecodeContext (0x10000);
|
||||
else
|
||||
throw new NotImplementedException ("MIO Nemesis encoding not implemented");
|
||||
|
||||
int pcm_bitrate = (int)(m_info.SamplesPerSec * BitsPerSample * ChannelCount);
|
||||
var format = new GameRes.WaveFormat();
|
||||
format.FormatTag = 1;
|
||||
format.Channels = (ushort)ChannelCount;
|
||||
format.SamplesPerSecond = m_info.SamplesPerSec;
|
||||
format.BitsPerSample = (ushort)BitsPerSample;
|
||||
format.BlockAlign = (ushort)(BitsPerSample/8*format.Channels);
|
||||
format.AverageBytesPerSecond = (uint)pcm_bitrate/8;
|
||||
this.Format = format;
|
||||
m_decoded_stream = LoadChunks (erif);
|
||||
|
||||
if (0 != m_total_samples)
|
||||
m_bitrate = (int)(stream_size * 8 * m_info.SamplesPerSec / m_total_samples);
|
||||
this.PcmSize = m_total_samples * ChannelCount * BitsPerSample / 8;
|
||||
m_decoded_stream.Position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class MioChunk : MioDataHeader
|
||||
{
|
||||
public uint FirstSample;
|
||||
public long Position;
|
||||
public uint Size;
|
||||
}
|
||||
|
||||
class ChunkStream : Stream
|
||||
{
|
||||
Stream m_source;
|
||||
MioChunk m_chunk;
|
||||
|
||||
public ChunkStream (Stream source, MioChunk chunk)
|
||||
{
|
||||
m_source = source;
|
||||
m_chunk = chunk;
|
||||
m_source.Position = m_chunk.Position;
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return true; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
public override bool CanSeek { get { return m_source.CanSeek; } }
|
||||
public override long Length { get { return m_chunk.Size; } }
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { return m_source.Position-m_chunk.Position; }
|
||||
set { Seek (value, SeekOrigin.Begin); }
|
||||
}
|
||||
|
||||
public override long Seek (long offset, SeekOrigin origin)
|
||||
{
|
||||
if (origin == SeekOrigin.Begin)
|
||||
offset += m_chunk.Position;
|
||||
else if (origin == SeekOrigin.Current)
|
||||
offset += m_source.Position;
|
||||
else
|
||||
offset += m_chunk.Position + m_chunk.Size;
|
||||
if (offset < m_chunk.Position)
|
||||
offset = m_chunk.Position;
|
||||
m_source.Position = offset;
|
||||
return offset - m_chunk.Position;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
m_source.Flush();
|
||||
}
|
||||
|
||||
public override int Read (byte[] buf, int index, int count)
|
||||
{
|
||||
long remaining = (m_chunk.Position + m_chunk.Size) - m_source.Position;
|
||||
if (count > remaining)
|
||||
count = (int)remaining;
|
||||
if (count <= 0)
|
||||
return 0;
|
||||
return m_source.Read (buf, index, count);
|
||||
}
|
||||
|
||||
public override void SetLength (long length)
|
||||
{
|
||||
throw new System.NotSupportedException ();
|
||||
}
|
||||
|
||||
public override void Write (byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new System.NotSupportedException ();
|
||||
}
|
||||
|
||||
public override void WriteByte (byte value)
|
||||
{
|
||||
throw new System.NotSupportedException ();
|
||||
}
|
||||
}
|
||||
|
||||
private Stream LoadChunks (EriFile erif)
|
||||
{
|
||||
uint current_sample = 0;
|
||||
List<MioChunk> chunks = new List<MioChunk>();
|
||||
try
|
||||
{
|
||||
erif.BaseStream.Position = m_stream_pos;
|
||||
for (;;)
|
||||
{
|
||||
long chunk_length = erif.FindSection ("SoundStm");
|
||||
if (chunk_length > int.MaxValue)
|
||||
throw new FileSizeException();
|
||||
var chunk = new MioChunk();
|
||||
chunk.FirstSample = current_sample;
|
||||
chunk.Version = erif.ReadByte();
|
||||
chunk.Flags = erif.ReadByte();
|
||||
erif.ReadInt16();
|
||||
chunk.SampleCount = erif.ReadUInt32();
|
||||
chunk.Position = erif.BaseStream.Position;
|
||||
chunk.Size = (uint)(chunk_length - 8);
|
||||
current_sample += chunk.SampleCount;
|
||||
chunks.Add (chunk);
|
||||
erif.BaseStream.Seek (chunk.Size, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException) { /* ignore EOF errors */ }
|
||||
m_total_samples = current_sample;
|
||||
if (0 == m_total_samples)
|
||||
{
|
||||
m_decode_finished = true;
|
||||
return Stream.Null;
|
||||
}
|
||||
uint sample_bytes = (uint)ChannelCount * BitsPerSample / 8;
|
||||
var total_bytes = m_total_samples * sample_bytes;
|
||||
|
||||
m_wait_handles = new WaitHandle[2] { m_available_chunk, m_decode_complete };
|
||||
m_chunk_queue = new ConcurrentQueue<byte[]>();
|
||||
m_worker = new BackgroundWorker();
|
||||
m_worker.WorkerSupportsCancellation = true;
|
||||
m_worker.DoWork += DoWork_Decode;
|
||||
m_worker.RunWorkerAsync (chunks);
|
||||
return new MemoryStream ((int)total_bytes);
|
||||
}
|
||||
|
||||
bool m_decode_finished = false;
|
||||
AutoResetEvent m_decode_complete = new AutoResetEvent (false);
|
||||
AutoResetEvent m_available_chunk = new AutoResetEvent (false);
|
||||
WaitHandle[] m_wait_handles;
|
||||
|
||||
ConcurrentQueue<byte[]> m_chunk_queue;
|
||||
BackgroundWorker m_worker;
|
||||
Exception m_decode_error = null;
|
||||
|
||||
private void DoWork_Decode (object sender, DoWorkEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var chunks = e.Argument as IEnumerable<MioChunk>;
|
||||
uint sample_bytes = (uint)ChannelCount * BitsPerSample / 8;
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
}
|
||||
using (var input = new ChunkStream (Source, chunk))
|
||||
{
|
||||
var wave_buf = new byte[chunk.SampleCount * sample_bytes];
|
||||
m_pmioc.AttachInputFile (input);
|
||||
if (!m_pmiod.DecodeSound (m_pmioc, chunk, wave_buf, 0))
|
||||
throw new InvalidFormatException();
|
||||
m_chunk_queue.Enqueue (wave_buf);
|
||||
m_available_chunk.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception X)
|
||||
{
|
||||
Trace.WriteLine (X.Message, "[MIO]");
|
||||
m_decode_error = X;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_decode_complete.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private int Read_Threaded (byte[] buf, int idx, int count)
|
||||
{
|
||||
var current_pos = Position;
|
||||
int total_read = 0;
|
||||
if (current_pos < m_decoded_stream.Length)
|
||||
{
|
||||
int available_bytes = (int)(m_decoded_stream.Length - current_pos);
|
||||
int read = m_decoded_stream.Read (buf, idx, Math.Min (count, available_bytes));
|
||||
idx += read;
|
||||
count -= read;
|
||||
total_read += read;
|
||||
}
|
||||
if (count > 0 && (!m_decode_finished || m_chunk_queue.Count > 0))
|
||||
{
|
||||
current_pos = Position;
|
||||
m_decoded_stream.Seek (0, SeekOrigin.End);
|
||||
for (;;)
|
||||
{
|
||||
byte[] wave_buf = null;
|
||||
while (m_chunk_queue.TryDequeue (out wave_buf))
|
||||
{
|
||||
m_decoded_stream.Write (wave_buf, 0, wave_buf.Length);
|
||||
if (current_pos + count <= m_decoded_stream.Length)
|
||||
break;
|
||||
}
|
||||
if (m_decode_finished || (current_pos + count <= m_decoded_stream.Length))
|
||||
break;
|
||||
int evt = WaitHandle.WaitAny (m_wait_handles);
|
||||
if (1 == evt)
|
||||
{
|
||||
m_decode_finished = true;
|
||||
if (m_decode_error != null)
|
||||
{
|
||||
m_decoded_stream.Position = current_pos;
|
||||
throw m_decode_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_decoded_stream.Position = current_pos;
|
||||
total_read += m_decoded_stream.Read (buf, idx, count);
|
||||
}
|
||||
return total_read;
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
bool _mio_disposed = false;
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (!_mio_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!m_decode_finished)
|
||||
{
|
||||
m_worker.CancelAsync();
|
||||
m_decode_complete.WaitOne();
|
||||
}
|
||||
if (m_decoded_stream != null)
|
||||
m_decoded_stream.Dispose();
|
||||
m_decode_complete.Dispose();
|
||||
m_available_chunk.Dispose();
|
||||
}
|
||||
_mio_disposed = true;
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1375
ArcFormats/Entis/EriReader.cs
Normal file
1375
ArcFormats/Entis/EriReader.cs
Normal file
File diff suppressed because it is too large
Load Diff
229
ArcFormats/Entis/ImageERI.cs
Normal file
229
ArcFormats/Entis/ImageERI.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
//! \file ImageERI.cs
|
||||
//! \date Tue May 26 12:04:30 2015
|
||||
//! \brief Entis rasterized image format.
|
||||
//
|
||||
// Copyright (C) 2015 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.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Windows.Media;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Entis
|
||||
{
|
||||
internal class EriMetaData : ImageMetaData
|
||||
{
|
||||
public int StreamPos;
|
||||
public int Version;
|
||||
public CvType Transformation;
|
||||
public EriCode Architecture;
|
||||
public int FormatType;
|
||||
public bool VerticalFlip;
|
||||
public int ClippedPixel;
|
||||
public int SamplingFlags;
|
||||
public ulong QuantumizedBits;
|
||||
public ulong AllottedBits;
|
||||
public int BlockingDegree;
|
||||
public int LappedBlock;
|
||||
public int FrameTransform;
|
||||
public int FrameDegree;
|
||||
}
|
||||
|
||||
public enum CvType
|
||||
{
|
||||
Lossless_ERI = 0x03020000,
|
||||
DCT_ERI = 0x00000001,
|
||||
LOT_ERI = 0x00000005,
|
||||
LOT_ERI_MSS = 0x00000105,
|
||||
}
|
||||
|
||||
public enum EriCode
|
||||
{
|
||||
RunlengthGamma = -1,
|
||||
RunlengthHuffman = -4,
|
||||
Nemesis = -16,
|
||||
}
|
||||
|
||||
public enum EriImage
|
||||
{
|
||||
RGB = 0x00000001,
|
||||
RGBA = 0x04000001,
|
||||
Gray = 0x00000002,
|
||||
TypeMask = 0x00FFFFFF,
|
||||
WithPalette = 0x01000000,
|
||||
UseClipping = 0x02000000,
|
||||
WithAlpha = 0x04000000,
|
||||
SideBySide = 0x10000000,
|
||||
}
|
||||
|
||||
internal class EriFile : BinaryReader
|
||||
{
|
||||
internal struct Section
|
||||
{
|
||||
public AsciiString Id;
|
||||
public long Length;
|
||||
}
|
||||
|
||||
public EriFile (Stream stream) : base (stream, System.Text.Encoding.ASCII, true)
|
||||
{
|
||||
}
|
||||
|
||||
public Section ReadSection ()
|
||||
{
|
||||
var section = new Section();
|
||||
section.Id = new AsciiString (8);
|
||||
if (8 != this.Read (section.Id.Value, 0, 8))
|
||||
throw new EndOfStreamException();
|
||||
section.Length = this.ReadInt64();
|
||||
return section;
|
||||
}
|
||||
|
||||
public long FindSection (string name)
|
||||
{
|
||||
var id = new AsciiString (8);
|
||||
for (;;)
|
||||
{
|
||||
if (8 != this.Read (id.Value, 0, 8))
|
||||
throw new EndOfStreamException();
|
||||
var length = this.ReadInt64();
|
||||
if (length < 0)
|
||||
throw new EndOfStreamException();
|
||||
if (id == name)
|
||||
return length;
|
||||
this.BaseStream.Seek (length, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class EriFormat : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "ERI"; } }
|
||||
public override string Description { get { return "Entis rasterized image format"; } }
|
||||
public override uint Signature { get { return 0x69746e45u; } } // 'Enti'
|
||||
|
||||
public override ImageMetaData ReadMetaData (Stream stream)
|
||||
{
|
||||
byte[] header = new byte[0x40];
|
||||
if (header.Length != stream.Read (header, 0, header.Length))
|
||||
return null;
|
||||
if (0x03000100 != LittleEndian.ToUInt32 (header, 8))
|
||||
return null;
|
||||
if (!Binary.AsciiEqual (header, 0x10, "Entis Rasterized Image"))
|
||||
return null;
|
||||
using (var reader = new EriFile (stream))
|
||||
{
|
||||
var section = reader.ReadSection();
|
||||
if (section.Id != "Header " || section.Length <= 0)
|
||||
return null;
|
||||
int header_size = (int)section.Length;
|
||||
int stream_pos = 0x50 + header_size;
|
||||
EriMetaData info = null;
|
||||
while (header_size > 8)
|
||||
{
|
||||
section = reader.ReadSection();
|
||||
header_size -= 8;
|
||||
if (section.Length <= 0 || section.Length > header_size)
|
||||
break;
|
||||
if ("ImageInf" == section.Id)
|
||||
{
|
||||
int version = reader.ReadInt32();
|
||||
if (version != 0x00020100 && version != 0x00020200)
|
||||
return null;
|
||||
info = new EriMetaData { StreamPos = stream_pos, Version = version };
|
||||
info.Transformation = (CvType)reader.ReadInt32();
|
||||
info.Architecture = (EriCode)reader.ReadInt32();
|
||||
info.FormatType = reader.ReadInt32();
|
||||
int w = reader.ReadInt32();
|
||||
int h = reader.ReadInt32();
|
||||
info.Width = (uint)Math.Abs (w);
|
||||
info.Height = (uint)Math.Abs (h);
|
||||
info.VerticalFlip = h < 0;
|
||||
info.BPP = reader.ReadInt32();
|
||||
info.ClippedPixel = reader.ReadInt32();
|
||||
info.SamplingFlags = reader.ReadInt32();
|
||||
info.QuantumizedBits = reader.ReadUInt64();
|
||||
info.AllottedBits = reader.ReadUInt64();
|
||||
info.BlockingDegree = reader.ReadInt32();
|
||||
info.LappedBlock = reader.ReadInt32();
|
||||
info.FrameTransform = reader.ReadInt32();
|
||||
info.FrameDegree = reader.ReadInt32();
|
||||
break;
|
||||
}
|
||||
header_size -= (int)section.Length;
|
||||
reader.BaseStream.Seek (section.Length, SeekOrigin.Current);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
public override ImageData Read (Stream stream, ImageMetaData info)
|
||||
{
|
||||
var meta = info as EriMetaData;
|
||||
if (null == meta)
|
||||
throw new ArgumentException ("EriFormat.Read should be supplied with EriMetaData", "info");
|
||||
stream.Position = meta.StreamPos;
|
||||
using (var input = new EriFile (stream))
|
||||
{
|
||||
Color[] palette = null;
|
||||
for (;;) // ReadSection throws an exception in case of EOF
|
||||
{
|
||||
var section = input.ReadSection();
|
||||
if ("Stream " == section.Id)
|
||||
continue;
|
||||
if ("ImageFrm" == section.Id)
|
||||
break;
|
||||
if ("Palette " == section.Id && info.BPP <= 8 && section.Length <= 0x400)
|
||||
{
|
||||
palette = ReadPalette (stream, (int)section.Length);
|
||||
continue;
|
||||
}
|
||||
input.BaseStream.Seek (section.Length, SeekOrigin.Current);
|
||||
}
|
||||
var reader = new EriReader (stream, meta, palette);
|
||||
reader.DecodeImage();
|
||||
return ImageData.Create (info, reader.Format, reader.Palette, reader.Data, reader.Stride);
|
||||
}
|
||||
}
|
||||
|
||||
private Color[] ReadPalette (Stream input, int palette_length)
|
||||
{
|
||||
var palette_data = new byte[0x400];
|
||||
if (palette_length > palette_data.Length)
|
||||
throw new InvalidFormatException();
|
||||
if (palette_length != input.Read (palette_data, 0, palette_length))
|
||||
throw new InvalidFormatException();
|
||||
var colors = new Color[256];
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
colors[i] = Color.FromRgb (palette_data[i*4+2], palette_data[i*4+1], palette_data[i*4]);
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
public override void Write (Stream file, ImageData image)
|
||||
{
|
||||
throw new NotImplementedException ("EriFormat.Write not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
1307
ArcFormats/Entis/MioDecoder.cs
Normal file
1307
ArcFormats/Entis/MioDecoder.cs
Normal file
File diff suppressed because it is too large
Load Diff
10
ArcFormats/Entis/WidgetNOA.xaml
Normal file
10
ArcFormats/Entis/WidgetNOA.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<Grid x:Class="GameRes.Formats.GUI.WidgetNOA"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:entis="clr-namespace:GameRes.Formats.Entis"
|
||||
xmlns:p="clr-namespace:GameRes.Formats.Properties"
|
||||
MaxWidth="300">
|
||||
<ComboBox Name="Scheme" ItemsSource="{Binding Source={x:Static entis:NoaOpener.KnownKeys}, Path=Keys, Mode=OneWay}"
|
||||
SelectedValue="{Binding Source={x:Static p:Settings.Default}, Path=NOAScheme, Mode=TwoWay}"
|
||||
Width="200"/>
|
||||
</Grid>
|
||||
18
ArcFormats/Entis/WidgetNOA.xaml.cs
Normal file
18
ArcFormats/Entis/WidgetNOA.xaml.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace GameRes.Formats.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for WidgetNOA.xaml
|
||||
/// </summary>
|
||||
public partial class WidgetNOA : Grid
|
||||
{
|
||||
public WidgetNOA ()
|
||||
{
|
||||
InitializeComponent ();
|
||||
// select first scheme as default
|
||||
if (-1 == Scheme.SelectedIndex)
|
||||
Scheme.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user