Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Crsky 2023-09-10 18:37:21 +08:00
commit ae9eb98d03
61 changed files with 4445 additions and 320 deletions

View File

@ -235,76 +235,85 @@ namespace GameRes.Formats.Ankh
Stream OpenTpw (ArcFile arc, PackedEntry entry) Stream OpenTpw (ArcFile arc, PackedEntry entry)
{ {
var output = new byte[entry.UnpackedSize];
using (var input = arc.File.CreateStream (entry.Offset, entry.Size)) using (var input = arc.File.CreateStream (entry.Offset, entry.Size))
{ {
input.Position = 8; var output = new byte[entry.UnpackedSize];
var offsets = new int[4]; UnpackTpw (input, output);
offsets[0] = input.ReadUInt16(); return new BinMemoryStream (output, entry.Name);
offsets[1] = offsets[0] * 2; }
offsets[2] = offsets[0] * 3; }
offsets[3] = offsets[0] * 4;
int dst = 0; internal static void UnpackTpw (IBinaryStream input, byte[] output)
while (dst < output.Length) {
input.Position = 8;
var offsets = new int[4];
offsets[0] = input.ReadUInt16();
offsets[1] = offsets[0] * 2;
offsets[2] = offsets[0] * 3;
offsets[3] = offsets[0] * 4;
int dst = 0;
while (dst < output.Length)
{
byte ctl = input.ReadUInt8();
if (0 == ctl)
break;
int remaining = output.Length - dst;
int count;
if (ctl < 0x40)
{ {
byte ctl = input.ReadUInt8(); count = Math.Min (ctl, remaining);
if (0 == ctl) input.Read (output, dst, count);
break; dst += count;
int count; }
if (ctl < 0x40) else if (ctl <= 0x6F)
{ {
count = Math.Min (ctl, output.Length - dst); if (0x6F == ctl)
input.Read (output, dst, count); count = input.ReadUInt16();
dst += count;
}
else if (ctl <= 0x6F)
{
if (0x6F == ctl)
count = input.ReadUInt16();
else
count = ctl - 0x3D;
byte v = input.ReadUInt8();
while (count --> 0)
output[dst++] = v;
}
else if (ctl <= 0x9F)
{
if (ctl == 0x9F)
count = input.ReadUInt16();
else
count = ctl - 0x6E;
byte v1 = input.ReadUInt8();
byte v2 = input.ReadUInt8();
while (count --> 0)
{
output[dst++] = v1;
output[dst++] = v2;
}
}
else if (ctl <= 0xBF)
{
if (ctl == 0xBF)
count = input.ReadUInt16();
else
count = ctl - 0x9E;
input.Read (output, dst, 3);
if (count > 0)
{
count *= 3;
Binary.CopyOverlapped (output, dst, dst+3, count-3);
dst += count;
}
}
else else
count = (byte)(ctl - 0x3D);
count = Math.Min (count, remaining);
byte v = input.ReadUInt8();
while (count --> 0)
output[dst++] = v;
}
else if (ctl <= 0x9F)
{
if (ctl == 0x9F)
count = input.ReadUInt16();
else
count = (byte)(ctl - 0x6E);
dst += input.Read (output, dst, Math.Min (2, remaining));
--count;
if (count > 0 && remaining > 2)
{ {
count = (ctl & 0x3F) + 3; count = Math.Min (count * 2, remaining - 2);
int offset = input.ReadUInt8(); Binary.CopyOverlapped (output, dst-2, dst, count);
offset = (offset & 0x3F) - offsets[offset >> 6];
Binary.CopyOverlapped (output, dst+offset, dst, count);
dst += count; dst += count;
} }
} }
return new BinMemoryStream (output, entry.Name); else if (ctl <= 0xBF)
{
if (ctl == 0xBF)
count = input.ReadUInt16();
else
count = (byte)(ctl - 0x9E);
dst += input.Read (output, dst, Math.Min (3, remaining));
--count;
if (count > 0 && remaining > 3)
{
count = Math.Min (count * 3, remaining - 3);
Binary.CopyOverlapped (output, dst-3, dst, count);
dst += count;
}
}
else
{
count = Math.Min ((ctl & 0x3F) + 3, remaining);
int offset = input.ReadUInt8();
offset = (offset & 0x3F) - offsets[offset >> 6];
Binary.CopyOverlapped (output, dst+offset, dst, count);
dst += count;
}
} }
} }
} }

View File

@ -123,6 +123,11 @@
<Compile Include="AdvSys\ImageGWD.cs" /> <Compile Include="AdvSys\ImageGWD.cs" />
<Compile Include="Ail\ArcLNK2.cs" /> <Compile Include="Ail\ArcLNK2.cs" />
<Compile Include="AIRNovel\ArcAIR.cs" /> <Compile Include="AIRNovel\ArcAIR.cs" />
<Compile Include="GScripter\ArcDATA.cs" />
<Compile Include="Ism\ImagePNG.cs" />
<Compile Include="Kogado\ArcARC.cs" />
<Compile Include="Ice\ImageIBM.cs" />
<Compile Include="Ice\ScriptISD.cs" />
<Compile Include="BlueGale\ImageBBM.cs" /> <Compile Include="BlueGale\ImageBBM.cs" />
<Compile Include="ClickTeam\ArcMF.cs" /> <Compile Include="ClickTeam\ArcMF.cs" />
<Compile Include="DxLib\ArcDX8.cs" /> <Compile Include="DxLib\ArcDX8.cs" />
@ -154,13 +159,19 @@
<Compile Include="Apricot\ArcDAT.cs" /> <Compile Include="Apricot\ArcDAT.cs" />
<Compile Include="ArcARCX.cs" /> <Compile Include="ArcARCX.cs" />
<Compile Include="Artemis\ArcMJA.cs" /> <Compile Include="Artemis\ArcMJA.cs" />
<Compile Include="Macintosh\ImagePICT.cs" />
<Compile Include="Macromedia\ArcDXR.cs" /> <Compile Include="Macromedia\ArcDXR.cs" />
<Compile Include="Macromedia\AudioSND.cs" />
<Compile Include="Macromedia\DirectorFile.cs" /> <Compile Include="Macromedia\DirectorFile.cs" />
<Compile Include="Macromedia\Palettes.cs" /> <Compile Include="Macromedia\Palettes.cs" />
<Compile Include="Mugi\ArcBIN.cs" />
<Compile Include="NScripter\Script.cs" /> <Compile Include="NScripter\Script.cs" />
<Compile Include="ScrPlayer\ImageIMG.cs" />
<Compile Include="Software House Parsley\ArcScn.cs" />
<Compile Include="TechGian\ArcBIN.cs" /> <Compile Include="TechGian\ArcBIN.cs" />
<Compile Include="Unity\ScriptDSM.cs" />
<Compile Include="Zyx\ImageXMG.cs" /> <Compile Include="Zyx\ImageXMG.cs" />
<None Include="AudioAIFF.cs" /> <Compile Include="AudioAIFF.cs" />
<Compile Include="Basil\ArcMIF.cs" /> <Compile Include="Basil\ArcMIF.cs" />
<Compile Include="Basil\AudioWHC.cs" /> <Compile Include="Basil\AudioWHC.cs" />
<Compile Include="Basil\ImageBCF.cs" /> <Compile Include="Basil\ImageBCF.cs" />
@ -624,7 +635,7 @@
</Compile> </Compile>
<Compile Include="Glib2\ArcG2.cs" /> <Compile Include="Glib2\ArcG2.cs" />
<Compile Include="Glib2\ImagePGX.cs" /> <Compile Include="Glib2\ImagePGX.cs" />
<Compile Include="ImagePSD.cs" /> <Compile Include="Adobe\ImagePSD.cs" />
<Compile Include="Kaguya\ArcANM.cs" /> <Compile Include="Kaguya\ArcANM.cs" />
<Compile Include="Kaguya\ArcLINK.cs" /> <Compile Include="Kaguya\ArcLINK.cs" />
<Compile Include="Kaguya\ImageAO.cs" /> <Compile Include="Kaguya\ImageAO.cs" />
@ -768,7 +779,7 @@
<Compile Include="ArcIKS.cs" /> <Compile Include="ArcIKS.cs" />
<Compile Include="ArcPACKDAT.cs" /> <Compile Include="ArcPACKDAT.cs" />
<Compile Include="CatSystem\ArcINT.cs" /> <Compile Include="CatSystem\ArcINT.cs" />
<Compile Include="ArcISA.cs" /> <Compile Include="Ism\ArcISA.cs" />
<Compile Include="Kaas\ArcKAAS.cs" /> <Compile Include="Kaas\ArcKAAS.cs" />
<Compile Include="Kaguya\ArcKaguya.cs" /> <Compile Include="Kaguya\ArcKaguya.cs" />
<Compile Include="Selene\ArcKCAP.cs" /> <Compile Include="Selene\ArcKCAP.cs" />
@ -849,6 +860,10 @@
<None Include="packages.config" /> <None Include="packages.config" />
<EmbeddedResource Include="Strings\arcStrings.ja-JP.resx" /> <EmbeddedResource Include="Strings\arcStrings.ja-JP.resx" />
<EmbeddedResource Include="Unity\strings.dat" /> <EmbeddedResource Include="Unity\strings.dat" />
<EmbeddedResource Include="ScrPlayer\ImgControlTable1" />
<EmbeddedResource Include="ScrPlayer\ImgControlTable2" />
<EmbeddedResource Include="ScrPlayer\ImgDeltaTable2" />
<EmbeddedResource Include="ScrPlayer\ImgControlTable32" />
<None Include="WebP\Alpha.cs" /> <None Include="WebP\Alpha.cs" />
<None Include="WebP\Decoder.cs"> <None Include="WebP\Decoder.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
@ -986,7 +1001,7 @@
<Compile Include="CatSystem\ImageHG3.cs" /> <Compile Include="CatSystem\ImageHG3.cs" />
<Compile Include="Triangle\ImageIAF.cs" /> <Compile Include="Triangle\ImageIAF.cs" />
<Compile Include="Silky\ImageIGF.cs" /> <Compile Include="Silky\ImageIGF.cs" />
<Compile Include="ImageISG.cs" /> <Compile Include="Ism\ImageISG.cs" />
<Compile Include="Kaas\ImageKAAS.cs" /> <Compile Include="Kaas\ImageKAAS.cs" />
<Compile Include="MAI\ImageMAI.cs" /> <Compile Include="MAI\ImageMAI.cs" />
<Compile Include="FC01\ImageMCG.cs" /> <Compile Include="FC01\ImageMCG.cs" />
@ -1059,11 +1074,11 @@
</Compile> </Compile>
<EmbeddedResource Include="Softpal\WaveTable1" /> <EmbeddedResource Include="Softpal\WaveTable1" />
<EmbeddedResource Include="Softpal\WaveTable2" /> <EmbeddedResource Include="Softpal\WaveTable2" />
<EmbeddedResource Include="ScrPlayer\ColorBitsTable1" /> <EmbeddedResource Include="ScrPlayer\IColorBitsTable1" />
<EmbeddedResource Include="ScrPlayer\ColorBitsTable2" /> <EmbeddedResource Include="ScrPlayer\IColorBitsTable2" />
<EmbeddedResource Include="ScrPlayer\ControlTable1" /> <EmbeddedResource Include="ScrPlayer\IControlTable1" />
<EmbeddedResource Include="ScrPlayer\ControlTable2" /> <EmbeddedResource Include="ScrPlayer\IControlTable2" />
<EmbeddedResource Include="ScrPlayer\ControlTable32" /> <EmbeddedResource Include="ScrPlayer\IControlTable32" />
<None Include="Resources\Formats.dat" /> <None Include="Resources\Formats.dat" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -26,6 +26,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using GameRes.Utility; using GameRes.Utility;
@ -302,33 +303,99 @@ namespace GameRes.Formats
public ResourceAccessor (string filename) public ResourceAccessor (string filename)
{ {
const uint LOAD_LIBRARY_AS_DATAFILE = 0x02;
const uint LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20; const uint LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20;
m_exe = NativeMethods.LoadLibraryEx (filename, IntPtr.Zero, LOAD_LIBRARY_AS_IMAGE_RESOURCE); m_exe = NativeMethods.LoadLibraryEx (filename, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE|LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if (IntPtr.Zero == m_exe) if (IntPtr.Zero == m_exe)
throw new Win32Exception (Marshal.GetLastWin32Error()); throw new Win32Exception (Marshal.GetLastWin32Error());
} }
public byte[] GetResource (string name, string type) public byte[] GetResource (string name, string type)
{ {
if (m_disposed) var res = FindResource (name, type);
throw new ObjectDisposedException ("Access to disposed ResourceAccessor object failed.");
var res = NativeMethods.FindResource (m_exe, name, type);
if (IntPtr.Zero == res) if (IntPtr.Zero == res)
return null; return null;
var glob = NativeMethods.LoadResource (m_exe, res); var src = LockResource (res);
if (IntPtr.Zero == glob)
return null;
uint size = NativeMethods.SizeofResource (m_exe, res);
var src = NativeMethods.LockResource (glob);
if (IntPtr.Zero == src) if (IntPtr.Zero == src)
return null; return null;
uint size = NativeMethods.SizeofResource (m_exe, res);
var dst = new byte[size]; var dst = new byte[size];
Marshal.Copy (src, dst, 0, dst.Length); Marshal.Copy (src, dst, 0, dst.Length);
return dst; return dst;
} }
public int ReadResource (string name, string type, byte[] dest, int pos)
{
var res = FindResource (name, type);
if (IntPtr.Zero == res)
return 0;
var src = LockResource (res);
if (IntPtr.Zero == src)
return 0;
int length = (int)NativeMethods.SizeofResource (m_exe, res);
length = Math.Min (dest.Length - pos, length);
Marshal.Copy (src, dest, pos, length);
return length;
}
public uint GetResourceSize (string name, string type)
{
var res = FindResource (name, type);
if (IntPtr.Zero == res)
return 0;
return NativeMethods.SizeofResource (m_exe, res);
}
private IntPtr FindResource (string name, string type)
{
if (m_disposed)
throw new ObjectDisposedException ("Access to disposed ResourceAccessor object failed.");
return NativeMethods.FindResource (m_exe, name, type);
}
private IntPtr LockResource (IntPtr res)
{
var glob = NativeMethods.LoadResource (m_exe, res);
if (IntPtr.Zero == glob)
return IntPtr.Zero;
return NativeMethods.LockResource (glob);
}
public IEnumerable<string> EnumTypes ()
{
var types = new List<string>();
if (!NativeMethods.EnumResourceTypes (m_exe, (m, t, p) => AddResourceName (types, t), IntPtr.Zero))
return Enumerable.Empty<string>();
return types;
}
public IEnumerable<string> EnumNames (string type)
{
var names = new List<string>();
if (!NativeMethods.EnumResourceNames (m_exe, type, (m, t, n, p) => AddResourceName (names, n), IntPtr.Zero))
return Enumerable.Empty<string>();
return names;
}
private static bool AddResourceName (List<string> list, IntPtr name)
{
list.Add (ResourceNameToString (name));
return true;
}
private static string ResourceNameToString (IntPtr resName)
{
if ((resName.ToInt64() >> 16) == 0)
{
return "#" + resName.ToString();
}
else
{
return Marshal.PtrToStringUni (resName);
}
}
#region IDisposable implementation #region IDisposable implementation
bool m_disposed = false; bool m_disposed = false;
public void Dispose () public void Dispose ()
@ -363,7 +430,7 @@ namespace GameRes.Formats
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
static internal extern bool FreeLibrary (IntPtr hModule); static internal extern bool FreeLibrary (IntPtr hModule);
[DllImport( "kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static internal extern IntPtr FindResource (IntPtr hModule, string lpName, string lpType); static internal extern IntPtr FindResource (IntPtr hModule, string lpName, string lpType);
[DllImport("Kernel32.dll", SetLastError = true)] [DllImport("Kernel32.dll", SetLastError = true)]
@ -374,5 +441,22 @@ namespace GameRes.Formats
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
static internal extern IntPtr LockResource (IntPtr hResData); static internal extern IntPtr LockResource (IntPtr hResData);
internal delegate bool EnumResTypeProc (IntPtr hModule, IntPtr lpszType, IntPtr lParam);
internal delegate bool EnumResNameProc (IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam);
internal delegate bool EnumResLangProc (IntPtr hModule, IntPtr lpszType, IntPtr lpszName, ushort wIDLanguage, IntPtr lParam);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static internal extern bool EnumResourceTypes (IntPtr hModule, [MarshalAs(UnmanagedType.FunctionPtr)] EnumResTypeProc lpEnumFunc, IntPtr lParam);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static internal extern bool EnumResourceNames (IntPtr hModule, IntPtr lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static internal extern bool EnumResourceNames (IntPtr hModule, string lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static internal extern bool EnumResourceLanguages (IntPtr hModule, IntPtr lpszType, string lpName, EnumResLangProc lpEnumFunc, IntPtr lParam);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static internal extern bool EnumResourceLanguages (IntPtr hModule, string lpszType, string lpName, EnumResLangProc lpEnumFunc, IntPtr lParam);
} }
} }

View File

@ -2,7 +2,7 @@
//! \date 2019 Jan 01 //! \date 2019 Jan 01
//! \brief Family Adv System resource archive. //! \brief Family Adv System resource archive.
// //
// Copyright (C) 2019 by morkt // Copyright (C) 2019-2023 by morkt
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to // of this software and associated documentation files (the "Software"), to
@ -34,6 +34,11 @@ using GameRes.Utility;
namespace GameRes.Formats.FamilyAdvSystem namespace GameRes.Formats.FamilyAdvSystem
{ {
[Serializable]
public class FamilyAdvScheme : ResourceScheme
{
public IDictionary<string, string> KnownKeys;
}
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public class CsafOpener : ArchiveFormat public class CsafOpener : ArchiveFormat
@ -70,8 +75,9 @@ namespace GameRes.Formats.FamilyAdvSystem
{ {
if (is_encrypted) if (is_encrypted)
{ {
var key = QueryEncryptionKey (file);
file.View.Read (0x20, index, 0, index_size); file.View.Read (0x20, index, 0, index_size);
enc = new CsafEncryption (DefaultKey, DefaultIV); enc = new CsafEncryption (key, DefaultIV);
using (var decryptor = enc.CreateDecryptor (0)) using (var decryptor = enc.CreateDecryptor (0))
using (var enc_names = file.CreateStream (0x20 + index_size, names_size)) using (var enc_names = file.CreateStream (0x20 + index_size, names_size))
using (var dec_names = new InputCryptoStream (enc_names, decryptor)) using (var dec_names = new InputCryptoStream (enc_names, decryptor))
@ -136,6 +142,27 @@ namespace GameRes.Formats.FamilyAdvSystem
var input = new CsafStream (carc); var input = new CsafStream (carc);
return new StreamRegion (input, entry.Offset, entry.Size); return new StreamRegion (input, entry.Offset, entry.Size);
} }
internal string QueryEncryptionKey (ArcView file)
{
var title = FormatCatalog.Instance.LookupGame (file.Name);
if (string.IsNullOrEmpty (title))
return DefaultKey;
string key;
if (!KnownKeys.TryGetValue (title, out key))
return DefaultKey;
return key;
}
FamilyAdvScheme DefaultScheme = new FamilyAdvScheme { KnownKeys = new Dictionary<string, string>() };
internal IDictionary<string, string> KnownKeys { get { return DefaultScheme.KnownKeys; } }
public override ResourceScheme Scheme
{
get { return DefaultScheme; }
set { DefaultScheme = (FamilyAdvScheme)value; }
}
} }
internal class CsafEncryption : IDisposable internal class CsafEncryption : IDisposable

View File

@ -0,0 +1,80 @@
//! \file ArcDATA.cs
//! \date 2023 Sep 06
//! \brief GScripter resource archive.
//
// Copyright (C) 2023 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.IO;
namespace GameRes.Formats.GScripter
{
[Export(typeof(ArchiveFormat))]
public class DataOpener : ArchiveFormat
{
public override string Tag { get => "DAT/GScripter"; }
public override string Description { get => "GScripter engine resource archive"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => false; }
public override bool CanWrite { get => false; }
public DataOpener ()
{
Extensions = new[] { "" };
}
public override ArcFile TryOpen (ArcView file)
{
var info_name = file.Name + ".info";
if (!VFS.FileExists (info_name))
return null;
using (var index = VFS.OpenView (info_name))
{
int count = (int)index.MaxOffset / 0x28;
if (!IsSaneCount (count) || count * 0x28 != index.MaxOffset)
return null;
var arc_name = Path.GetFileName (file.Name);
bool is_cg = arc_name.StartsWith ("CG");
bool is_sound = !is_cg && arc_name.StartsWith ("SOUND");
uint index_pos = 0;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = index.View.ReadString (index_pos, 0x20);
var entry = new Entry {
Name = name,
Type = is_cg ? "image" : is_sound ? "audio" : "",
Offset = index.View.ReadUInt32 (index_pos+0x20),
Size = index.View.ReadUInt32 (index_pos+0x24),
};
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_pos += 0x28;
}
return new ArcFile (file, this, dir);
}
}
}
}

View File

@ -0,0 +1,88 @@
//! \file ImageIBM.cs
//! \date 2023 Aug 25
//! \brief Ice Soft compressed bitmap format.
//
// Copyright (C) 2023 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media.Imaging;
using GameRes.Formats.Ankh;
// [000324][Juice] Orgel ~Kesenai Melody~
namespace GameRes.Formats.Ice
{
internal class IbmMetaData : ImageMetaData
{
public int UnpackedSize;
}
[Export(typeof(ImageFormat))]
public class IbmFormat : ImageFormat
{
public override string Tag { get => "IBM/ICE"; }
public override string Description { get => "Ice Soft compressed bitmap"; }
public override uint Signature { get => 0x01575054; } // 'TPW'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
file.Position = 4;
int unpacked_size = file.ReadInt32();
if (unpacked_size <= 0)
return null;
var bmp_header = new byte[56];
GrpOpener.UnpackTpw (file, bmp_header);
using (var bmp = new BinMemoryStream (bmp_header, file.Name))
{
var bmp_info = Bmp.ReadMetaData (bmp);
if (null == bmp_info)
return null;
return new IbmMetaData {
Width = bmp_info.Width,
Height = bmp_info.Height,
BPP = bmp_info.BPP,
UnpackedSize = unpacked_size,
};
}
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var meta = (IbmMetaData)info;
var output = new byte[meta.UnpackedSize];
GrpOpener.UnpackTpw (file, output);
using (var bmp = new BinMemoryStream (output, file.Name))
{
var decoder = new BmpBitmapDecoder (bmp, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
var frame = decoder.Frames[0];
frame.Freeze();
return new ImageData (frame, info);
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("IbmFormat.Write not implemented");
}
}
}

View File

@ -0,0 +1,58 @@
//! \file ScriptISD.cs
//! \date 2023 Aug 25
//! \brief Ice Soft compressed binary script.
//
// Copyright (C) 2023 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 GameRes.Formats.Ankh;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Ice
{
[Export(typeof(ScriptFormat))]
public class IsdScript : GenericScriptFormat
{
public override string Tag { get => "ISD"; }
public override string Description { get => "Ice Soft binary script"; }
public override uint Signature { get => 0x01575054; } // 'TPW'
public override bool IsScript (IBinaryStream file)
{
return file.Signature == Signature;
}
public override Stream ConvertFrom (IBinaryStream file)
{
file.Position = 4;
int unpacked_size = file.ReadInt32();
var data = new byte[unpacked_size];
GrpOpener.UnpackTpw (file, data);
return new BinMemoryStream (data, file.Name);
}
public override Stream ConvertBack (IBinaryStream file)
{
throw new System.NotImplementedException();
}
}
}

View File

@ -36,12 +36,26 @@ namespace GameRes.Formats
public override uint Signature { get { return 0; } } public override uint Signature { get { return 0; } }
public override bool CanWrite { get { return true; } } public override bool CanWrite { get { return true; } }
public MbImageFormat ()
{
Extensions = new[] { "bmp", "gra" };
}
public override ImageMetaData ReadMetaData (IBinaryStream stream) public override ImageMetaData ReadMetaData (IBinaryStream stream)
{ {
int c1 = stream.ReadByte(); int c1 = stream.ReadByte();
int c2 = stream.ReadByte(); int c2 = stream.ReadByte();
if ('M' != c1 || ('B' != c2 && 'C' != c2)) switch (c1)
return null; {
case 'M':
if ('B' != c2 && 'C' != c2)
return null;
break;
case 'C':
if ('L' != c2)
return null;
break;
}
using (var bmp = OpenAsBitmap (stream)) using (var bmp = OpenAsBitmap (stream))
return Bmp.ReadMetaData (bmp); return Bmp.ReadMetaData (bmp);
} }

View File

@ -48,7 +48,7 @@ namespace GameRes.Formats.Interheart
public CandyFormat () public CandyFormat ()
{ {
Signatures = new uint[] { 0x0E00, 0 }; Signatures = new uint[] { 0x0E00, 0x80020A00, 0 };
} }
public override ImageMetaData ReadMetaData (IBinaryStream file) public override ImageMetaData ReadMetaData (IBinaryStream file)
@ -82,7 +82,7 @@ namespace GameRes.Formats.Interheart
} }
else else
return null; return null;
if (info.Version != 1 && info.Version != 2 || info.BPP < 8 || info.BPP > 32) if (info.Version != 1 && info.Version != 2 || info.BPP < 1 || info.BPP > 32)
return null; return null;
return info; return info;
} }
@ -91,7 +91,7 @@ namespace GameRes.Formats.Interheart
{ {
var reader = new CandyDecoder (file, (CandyMetaData)info); var reader = new CandyDecoder (file, (CandyMetaData)info);
var pixels = reader.Unpack(); var pixels = reader.Unpack();
return ImageData.Create (info, reader.Format, reader.Palette, pixels); return ImageData.Create (info, reader.Format, reader.Palette, pixels, reader.Stride);
} }
public override void Write (Stream file, ImageData image) public override void Write (Stream file, ImageData image)
@ -113,6 +113,7 @@ namespace GameRes.Formats.Interheart
public PixelFormat Format { get; private set; } public PixelFormat Format { get; private set; }
public BitmapPalette Palette { get; private set; } public BitmapPalette Palette { get; private set; }
public int Stride { get { return m_stride; } }
public CandyDecoder (IBinaryStream input, CandyMetaData info) public CandyDecoder (IBinaryStream input, CandyMetaData info)
{ {
@ -133,6 +134,12 @@ namespace GameRes.Formats.Interheart
m_stride = m_width * 4; m_stride = m_width * 4;
Format = PixelFormats.Bgra32; Format = PixelFormats.Bgra32;
} }
else if (1 == info.BPP)
{
m_pixel_size = 1;
m_stride = (m_width + 7) / 8;
Format = PixelFormats.Indexed1;
}
else else
{ {
m_pixel_size = 1; m_pixel_size = 1;
@ -148,6 +155,8 @@ namespace GameRes.Formats.Interheart
if (m_colors > 0) if (m_colors > 0)
Palette = ReadPalette(); Palette = ReadPalette();
LzUnpack(); LzUnpack();
if (1 == m_info.BPP)
return m_output;
return RestorePixels(); return RestorePixels();
} }

View File

@ -37,6 +37,11 @@ namespace GameRes.Formats.ISM
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public IsaOpener ()
{
ContainedFormats = new[] { "ISG", "PNG/ISM", "OGG", };
}
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
if (!file.View.AsciiEqual (4, "ARCHIVED")) if (!file.View.AsciiEqual (4, "ARCHIVED"))
@ -87,6 +92,13 @@ namespace GameRes.Formats.ISM
entry.Size = m_file.View.ReadUInt32 (index_offset+8); entry.Size = m_file.View.ReadUInt32 (index_offset+8);
if (!entry.CheckPlacement (m_file.MaxOffset)) if (!entry.CheckPlacement (m_file.MaxOffset))
return null; return null;
if (string.IsNullOrEmpty (entry.Type) && name_length < 0x20) // try to fix truncated extension
{
if (name.EndsWith (".OG"))
entry.Type = "audio";
else if (name.EndsWith (".PN"))
entry.Type = "image";
}
m_dir.Add (entry); m_dir.Add (entry);
index_offset += record_length; index_offset += record_length;
} }

View File

@ -41,6 +41,7 @@ namespace GameRes.Formats.ISM
public int Colors; public int Colors;
public uint Packed; public uint Packed;
public uint Unpacked; public uint Unpacked;
public int RecursionDepth;
} }
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
@ -78,88 +79,46 @@ namespace GameRes.Formats.ISM
public override ImageData Read (IBinaryStream stream, ImageMetaData info) public override ImageData Read (IBinaryStream stream, ImageMetaData info)
{ {
var meta = (IsgMetaData)info; var meta = (IsgMetaData)info;
if (0x21 != meta.Type && 0x10 != meta.Type) if (0x21 != meta.Type && 0x10 != meta.Type && 0x34 != meta.Type)
throw new InvalidFormatException ("Unsupported ISM image type"); throw new InvalidFormatException ("Unsupported ISM image type");
stream.Position = 0x30; var input = new Reader (stream, meta);
using (var input = new Reader (stream, meta)) var pixels = input.Unpack (this);
{ var palette = new BitmapPalette (input.Palette);
if (0x21 == meta.Type) return ImageData.CreateFlipped (info, PixelFormats.Indexed8, palette, input.Data, info.iWidth);
input.Unpack21();
else
input.Unpack10();
var palette = new BitmapPalette (input.Palette);
return ImageData.CreateFlipped (info, PixelFormats.Indexed8, palette, input.Data, (int)info.Width);
}
} }
internal class Reader : IDisposable internal class Reader
{ {
IBinaryStream m_input; IBinaryStream m_input;
byte[] m_data; byte[] m_data;
Color[] m_palette;
int m_input_size; int m_input_size;
IsgMetaData m_info;
public Color[] Palette { get { return m_palette; } } public const int RecursionLimit = 32; // have seen 18 deep
public Color[] Palette { get; private set; }
public byte[] Data { get { return m_data; } } public byte[] Data { get { return m_data; } }
public Reader (IBinaryStream file, IsgMetaData info) public Reader (IBinaryStream file, IsgMetaData info)
{ {
int palette_size = (int)info.Colors*4;
var palette_data = new byte[Math.Max (0x400, palette_size)];
if (palette_size != file.Read (palette_data, 0, palette_size))
throw new InvalidFormatException();
m_palette = new Color[0x100];
for (int i = 0; i < m_palette.Length; ++i)
{
m_palette[i] = Color.FromRgb (palette_data[i*4+2], palette_data[i*4+1], palette_data[i*4]);
}
m_input = file; m_input = file;
m_input_size = (int)info.Packed; m_input_size = (int)info.Packed;
m_data = new byte[info.Width * info.Height]; m_info = info;
} }
public void Unpack21 () public byte[] Unpack (IsgFormat isg)
{ {
int dst = 0; m_input.Position = 0x30;
var frame = new byte[2048]; if (0x34 == m_info.Type)
int frame_pos = 2039; return Unpack34(isg);
int remaining = m_input_size; Palette = ImageFormat.ReadColorMap (m_input.AsStream);
byte ctl = m_input.ReadUInt8(); m_data = new byte[m_info.Width * m_info.Height];
--remaining; if (0x21 == m_info.Type)
int bit = 0x80; DecompressLzss (m_input_size, m_data);
while (remaining > 0) else
{ Unpack10();
if (0 != (ctl & bit)) return m_data;
{
byte hi = m_input.ReadUInt8();
byte lo = m_input.ReadUInt8();
remaining -= 2;
int offset = (hi & 7) << 8 | lo;
for (int count = (hi >> 3) + 3; count > 0; --count)
{
byte p = frame[offset];
frame[frame_pos] = p;
m_data[dst++] = p;
offset = (offset + 1) & 0x7ff;
frame_pos = (frame_pos + 1) & 0x7ff;
}
}
else
{
byte p = m_input.ReadUInt8();
--remaining;
m_data[dst++] = p;
frame[frame_pos] = p;
frame_pos = (frame_pos + 1) & 0x7ff;
}
if (0 == (bit >>= 1))
{
ctl = m_input.ReadUInt8();
--remaining;
bit = 0x80;
}
}
} }
public void Unpack10 () public void Unpack10 ()
@ -192,12 +151,112 @@ namespace GameRes.Formats.ISM
} }
} }
#region IDisposable Members public byte[] Unpack34 (IsgFormat isg)
public void Dispose ()
{ {
GC.SuppressFinalize (this); if (m_info.RecursionDepth >= RecursionLimit)
throw new InvalidFormatException ("Recursion limit reached for ISG image.");
var base_name = m_input.ReadCString (0x10);
var base_image = ReadBaseImage (base_name, isg);
if (null == base_image)
throw new InvalidFormatException ("Unable to read baseline ISG image.");
int count = m_input.ReadInt32();
int packed_size = m_input.ReadInt32();
int ovl_ctl_size = m_info.iWidth * m_info.iHeight / 128;
var overlay_info = m_input.ReadBytes (ovl_ctl_size);
var overlay_data = new byte[count * 32 + 8];
DecompressLzss (packed_size, overlay_data);
int bit_count = 0;
int ctl_src = 0;
int data_src = 0;
for (int y = 0; y < m_info.iHeight; y += 4)
for (int x = 0; x < m_info.iWidth; x += 4)
{
if (((1 << bit_count) & overlay_info[ctl_src]) != 0)
{
int dst = y * m_info.iWidth + x;
for (int r = 0; r < 4; ++r)
{
base_image[dst ] = overlay_data[data_src++];
base_image[dst+1] = overlay_data[data_src++];
base_image[dst+2] = overlay_data[data_src++];
base_image[dst+3] = overlay_data[data_src++];
dst += m_info.iWidth;
}
}
if (++bit_count == 8)
{
bit_count = 0;
++ctl_src;
}
}
return m_data = base_image;
}
internal void DecompressLzss (int remaining, byte[] output)
{
int dst = 0;
var frame = new byte[2048];
int frame_pos = 2039;
byte ctl = m_input.ReadUInt8();
--remaining;
int bit = 0x80;
while (remaining > 0)
{
if (0 != (ctl & bit))
{
byte hi = m_input.ReadUInt8();
byte lo = m_input.ReadUInt8();
remaining -= 2;
int offset = (hi & 7) << 8 | lo;
for (int count = (hi >> 3) + 3; count > 0; --count)
{
byte p = frame[offset];
frame[frame_pos] = p;
output[dst++] = p;
offset = (offset + 1) & 0x7ff;
frame_pos = (frame_pos + 1) & 0x7ff;
}
}
else
{
byte p = m_input.ReadUInt8();
--remaining;
output[dst++] = p;
frame[frame_pos] = p;
frame_pos = (frame_pos + 1) & 0x7ff;
}
if (0 == (bit >>= 1))
{
ctl = m_input.ReadUInt8();
--remaining;
bit = 0x80;
}
}
}
internal byte[] ReadBaseImage (string name, IsgFormat isg)
{
if (!VFS.FileExists (name))
{
if (name.Length <= 12)
return null;
name = name.Substring (0, 12);
if (!VFS.FileExists (name))
return null;
}
using (var base_file = VFS.OpenBinaryStream (name))
{
var base_info = isg.ReadMetaData (base_file) as IsgMetaData;
if (null == base_info || base_info.Width != m_info.Width || base_info.Height != m_info.Height)
throw new InvalidFormatException ("Invalid baseline ISG image.");
base_info.RecursionDepth = m_info.RecursionDepth + 1;
base_info.FileName = name;
var reader = new Reader (base_file, base_info);
var pixels = reader.Unpack (isg);
this.Palette = reader.Palette;
return pixels;
}
} }
#endregion
} }
} }
} }

View File

@ -0,0 +1,71 @@
//! \file ImagePNG.cs
//! \date 2023 Sep 02
//! \brief PNG image with inverted alpha-channel.
//
// Copyright (C) 2023 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Ism
{
[Export(typeof(ImageFormat))]
[ExportMetadata("Priority", -1)] // deprioritize
public class PngIsmFormat : ImageFormat
{
public override string Tag { get => "PNG/ISM"; }
public override string Description { get => "ISM engine PNG image"; }
public override uint Signature { get => 0x474e5089; }
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
// format only applied when extracting from related archive
if (!VFS.IsVirtual || VFS.CurrentArchive.Tag != "ISA")
return null;
return Png.ReadMetaData (file);
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var decoder = new PngBitmapDecoder (file.AsStream,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapSource bitmap = decoder.Frames[0];
if (bitmap.Format != PixelFormats.Bgra32)
return new ImageData (bitmap, info);
int stride = bitmap.PixelWidth * 4;
var pixels = new byte[stride * bitmap.PixelHeight];
bitmap.CopyPixels (pixels, stride, 0);
for (int i = 3; i < pixels.Length; i += 4)
{
pixels[i] ^= 0xFF;
}
return ImageData.Create (info, bitmap.Format, bitmap.Palette, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("PngIsmFormat.Write not implemented");
}
}
}

View File

@ -23,8 +23,11 @@
// IN THE SOFTWARE. // IN THE SOFTWARE.
// //
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Windows.Media; using System.Windows.Media;
namespace GameRes.Formats.Kaguya namespace GameRes.Formats.Kaguya
@ -98,4 +101,101 @@ namespace GameRes.Formats.Kaguya
return ImageData.CreateFlipped (Info, format, null, pixels, stride); return ImageData.CreateFlipped (Info, format, null, pixels, stride);
} }
} }
internal class Pl10Entry : An21Entry
{
public ImageMetaData Info;
}
[Export(typeof(ArchiveFormat))]
public class Pl10Opener : An21Opener
{
public override string Tag { get => "PL10"; }
public override uint Signature { get => 0x30314C50; } // 'PL10'
public Pl10Opener ()
{
Extensions = new string[] { "plt" };
}
public override ArcFile TryOpen (ArcView file)
{
using (var input = file.CreateStream())
{
var base_info = GetBaseInfo (input);
var dir = GetFramesList (input);
if (null == dir)
return null;
string base_name = Path.GetFileNameWithoutExtension (file.Name);
foreach (Pl10Entry entry in dir)
{
entry.Name = string.Format ("{0}#{1:D2}", base_name, entry.FrameIndex);
entry.Type = "image";
}
var first = (Pl10Entry)dir[0];
base_info.BPP = first.Info.BPP;
return new An21Archive (file, this, dir, base_info);
}
}
internal ImageMetaData GetBaseInfo (IBinaryStream input)
{
input.Position = 6;
return new ImageMetaData
{
OffsetX = input.ReadInt32(),
OffsetY = input.ReadInt32(),
Width = input.ReadUInt32(),
Height = input.ReadUInt32(),
};
}
internal List<Entry> GetFramesList (IBinaryStream file)
{
file.Position = 4;
int count = file.ReadInt16();
if (!IsSaneCount (count))
return null;
var dir = new List<Entry> (count);
long current_offset = 0x16;
file.Position = current_offset;
var frame_info = new ImageMetaData {
OffsetX = file.ReadInt32(),
OffsetY = file.ReadInt32(),
Width = file.ReadUInt32(),
Height = file.ReadUInt32(),
BPP = file.ReadInt32() * 8,
};
uint depth = (uint)frame_info.BPP / 8;
uint image_size = depth * frame_info.Width * frame_info.Height;
var entry = new Pl10Entry
{
Offset = current_offset + 0x14,
Size = image_size,
FrameIndex = 0,
RleStep = 0,
Info = frame_info,
};
dir.Add (entry);
for (int i = 1; i < count; ++i)
{
current_offset = entry.Offset + entry.Size;
file.Position = current_offset;
byte rle_step = file.ReadUInt8();
uint packed_size = file.ReadUInt32();
entry = new Pl10Entry
{
Offset = current_offset + 5,
Size = packed_size,
UnpackedSize = image_size,
IsPacked = true,
FrameIndex = i,
RleStep = rle_step,
Info = frame_info,
};
dir.Add (entry);
}
return dir;
}
}
} }

308
ArcFormats/Kogado/ArcARC.cs Normal file
View File

@ -0,0 +1,308 @@
//! \file ArcARC.cs
//! \date 2023 Aug 26
//! \brief Kogado resource archive.
//
// Copyright (C) 2023 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 GameRes.Compression;
using GameRes.Formats.DirectDraw;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Text;
using System.Windows.Media;
// [070302][G-mode] Keitai Shoujo
namespace GameRes.Formats.Kogado
{
[Export(typeof(ArchiveFormat))]
public class ArcOpener : ArchiveFormat
{
public override string Tag { get => "ARC/KOGADO"; }
public override string Description { get => "Kogado engine resource archive"; }
public override uint Signature { get => 0xA8BCADBE; } // 'ARCW' ^ 0xFFFFFFFF
public override bool IsHierarchic { get => true; }
public override bool CanWrite { get => false; }
public override ArcFile TryOpen (ArcView file)
{
using (var reader = new ArcIndexReader (file))
{
var dir = reader.ReadIndex();
return new ArcFile(file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
Stream input = arc.File.CreateStream (entry.Offset, entry.Size);
input = new XoredStream (input, 0xFF);
var ova = entry as OvaEntry;
if (ova != null)
return new PrefixStream (ova.Header, input);
var pent = entry as PackedEntry;
if (null == pent || !pent.IsPacked)
return input;
return new LimitStream (new LzssStream (input), pent.UnpackedSize);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var dds = entry as DdsEntry;
if (null == dds)
return base.OpenImage (arc, entry);
var input = this.OpenEntry (arc, entry);
return new ImageDecoder (input, dds.Info);
}
}
internal class OvaEntry : PackedEntry
{
public byte[] Header;
}
internal class DdsEntry : PackedEntry
{
public DdsInfo Info;
}
internal class DdsInfo : ImageMetaData
{
public DdsPF Flags;
}
internal sealed class ArcIndexReader : IDisposable
{
IBinaryStream m_input;
uint m_base_offset;
long m_max_offset;
public ArcIndexReader (ArcView arc)
{
m_base_offset = arc.View.ReadUInt32 (0xC);
m_max_offset = arc.MaxOffset;
m_input = arc.CreateStream();
}
byte[] m_filenames;
byte[] m_index;
List<Entry> m_dir;
public List<Entry> ReadIndex ()
{
m_input.Position = 0x10;
m_filenames = ReadChunk();
m_index = ReadChunk();
m_dir = new List<Entry>();
int section_count = m_index.ToInt32 (0);
int pos = 4;
for (int i = 0; i < section_count; ++i)
{
int count = m_index.ToInt32 (pos+8);
if (m_dir.Capacity < m_dir.Count + count)
m_dir.Capacity = m_dir.Count + count;
int section_size = m_index.ToInt32 (pos+0xC);
int name_pos = pos + 0x10;
int layout_pos = name_pos + 4 * count;
if (m_index.AsciiEqual (pos, "DDS\0"))
ReadDdsSection (name_pos, layout_pos, count);
else if (m_index.AsciiEqual (pos, "OVA\0"))
ReadOvaSection (name_pos, layout_pos, count);
else
ReadSection (name_pos, layout_pos, count);
pos += 0x10 + section_size;
}
return m_dir;
}
void ReadSection (int name_pos, int layout_pos, int count)
{
for (int j = 0; j < count; ++j)
{
var name = ReadFileName (m_index.ToInt32 (name_pos));
var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
entry.Offset = m_index.ToUInt32 (layout_pos) + m_base_offset;
entry.Size = m_index.ToUInt32 (layout_pos+4);
entry.UnpackedSize = m_index.ToUInt32 (layout_pos+0xC);
entry.IsPacked = true;
if (!entry.CheckPlacement (m_max_offset+0x14))
throw new InvalidFormatException();
m_dir.Add (entry);
name_pos += 4;
layout_pos += 0x10;
}
}
void ReadDdsSection (int name_pos, int layout_pos, int count)
{
int header_count = m_index.ToInt32 (layout_pos);
var headers = new DdsInfo[header_count];
layout_pos += 4;
for (int i = 0; i < header_count; ++i)
{
headers[i] = new DdsInfo {
Width = m_index.ToUInt32 (layout_pos+4),
Height = m_index.ToUInt32 (layout_pos+8),
BPP = 32,
Flags = (DdsPF)m_index.ToUInt32 (layout_pos),
};
layout_pos += 0xC;
}
for (int j = 0; j < count; ++j)
{
var name = ReadFileName (m_index.ToInt32 (name_pos));
var entry = FormatCatalog.Instance.Create<DdsEntry> (name);
entry.Offset = m_index.ToUInt32 (layout_pos) + m_base_offset;
entry.Size = m_index.ToUInt32 (layout_pos+4);
entry.UnpackedSize = m_index.ToUInt32 (layout_pos+0xC);
int header_id = m_index.ToInt32 (layout_pos+0x10);
entry.Info = headers[header_id];
entry.IsPacked = true;
if (!entry.CheckPlacement (m_max_offset+0x14))
throw new InvalidFormatException();
m_dir.Add (entry);
name_pos += 4;
layout_pos += 0x14;
}
}
void ReadOvaSection (int name_pos, int layout_pos, int count)
{
int header_count = m_index.ToInt32 (layout_pos+4);
var headers = new byte[header_count][];
layout_pos += 8;
for (int i = 0; i < header_count; ++i)
{
int header_len = m_index.ToInt32 (layout_pos+8);
int header_pos = layout_pos + 12;
headers[i] = new CowArray<byte> (m_index, header_pos, header_len).ToArray();
layout_pos = header_pos + header_len;
}
for (int j = 0; j < count; ++j)
{
var name = ReadFileName (m_index.ToInt32 (name_pos));
var entry = FormatCatalog.Instance.Create<OvaEntry> (name);
entry.Offset = m_index.ToUInt32 (layout_pos) + m_base_offset;
entry.UnpackedSize = m_index.ToUInt32 (layout_pos+4);
int header_id = m_index.ToInt32 (layout_pos+8);
entry.Header = headers[header_id];
entry.Size = entry.UnpackedSize - (uint)entry.Header.Length;
entry.IsPacked = true;
if (!entry.CheckPlacement (m_max_offset))
throw new InvalidFormatException();
m_dir.Add (entry);
name_pos += 4;
layout_pos += 0xC;
}
}
string ReadFileName (int pos)
{
int i;
for (i = pos; i+1 < m_filenames.Length; i += 2)
{
if (m_filenames[i] == 0 && m_filenames[i+1] == 0)
break;
}
return Encoding.Unicode.GetString (m_filenames, pos, i - pos);
}
byte[] ReadChunk ()
{
long start_pos = m_input.Position;
int size = m_input.ReadInt32();
int type = m_input.ReadInt32();
int unpacked_size = m_input.ReadInt32();
if (size <= 0 || unpacked_size <= 0)
throw new InvalidFormatException();
var data = new byte[unpacked_size];
using (var decrypted = new XoredStream (m_input.AsStream, 0xFF, true))
using (var lzss = new LzssStream (decrypted))
{
lzss.Read (data, 0, data.Length);
}
m_input.Position = start_pos + size;
return data;
}
#region IDisposable Members
bool _disposed = false;
public void Dispose ()
{
if (!_disposed)
{
m_input.Dispose();
_disposed = true;
}
GC.SuppressFinalize (this);
}
#endregion
}
internal sealed class ImageDecoder : IImageDecoder
{
Stream m_input;
ImageData m_image;
public Stream Source => m_input;
public ImageFormat SourceFormat => null;
public PixelFormat Format { get; private set; }
public ImageMetaData Info { get; private set; }
public ImageData Image => m_image ?? (m_image = GetImageData());
public ImageDecoder (Stream input, DdsInfo info)
{
m_input = input;
Info = info;
Format = info.Flags.HasFlag (DdsPF.AlphaPixels) ? PixelFormats.Bgra32 : PixelFormats.Bgr32;
}
private ImageData GetImageData ()
{
var pixels = new byte[Info.iWidth * Info.iHeight * 4];
m_input.Read (pixels, 0, pixels.Length);
return ImageData.Create (Info, Format, null, pixels);
}
#region IDisposable members
bool m_disposed = false;
public void Dispose ()
{
if (!m_disposed)
{
m_input.Dispose();
m_disposed = true;
}
GC.SuppressFinalize (this);
}
#endregion
}
}

View File

@ -0,0 +1,436 @@
//! \file ImagePICT.cs
//! \date 2023 Aug 24
//! \brief Macintosh picture format.
//
// Copyright (C) 2023 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 GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Apple
{
internal class PictMetaData : ImageMetaData
{
public uint DataOffset;
}
[Export(typeof(ImageFormat))]
public class PictFormat : ImageFormat
{
public override string Tag { get => "PICT/MAC"; }
public override string Description { get => "Apple Macintosh image format"; }
public override uint Signature { get => 0; }
public PictFormat ()
{
Signatures = new[] { 0u, 0x54434950u };
Extensions = new[] { "pct", "pict", "pic" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
int header_pos = 0x200;
if (file.Signature == 0x54434950) // 'PICT'
header_pos = 4;
if (file.Length < header_pos + 0x10)
return null;
file.Position = header_pos + 2;
short top = file.ReadI16BE();
short left = file.ReadI16BE();
short bottom = file.ReadI16BE();
short right = file.ReadI16BE();
if (file.ReadU16BE() != 0x11)
return null;
int version = file.ReadU16BE();
if (version != 0x2FF)
return null;
return new PictMetaData {
Width = (uint)(right - left),
Height = (uint)(bottom - top),
OffsetX = left,
OffsetY = top,
BPP = 32,
DataOffset = (uint)file.Position,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var decoder = new PictReader (file, (PictMetaData)info);
var pixels = decoder.Unpack();
return ImageData.Create (info, decoder.Format, decoder.Palette, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("PictFormat.Write not implemented");
}
}
internal static class BinaryStreamExtension
{
static public short ReadI16BE (this IBinaryStream file)
{
return Binary.BigEndian (file.ReadInt16());
}
static public ushort ReadU16BE (this IBinaryStream file)
{
return Binary.BigEndian (file.ReadUInt16());
}
static public int ReadI32BE (this IBinaryStream file)
{
return Binary.BigEndian (file.ReadInt32());
}
static public uint ReadU32BE (this IBinaryStream file)
{
return Binary.BigEndian (file.ReadUInt32());
}
}
internal class Pixmap
{
public short Version;
public short PackType;
public int PackSize;
public int HorizRes;
public int VertRes;
public short PixelType;
public short BPP;
public short CompCount;
public short CompSize;
public int PlaneBytes;
public int Table;
public void Deserialize (IBinaryStream input)
{
Version = input.ReadI16BE();
PackType = input.ReadI16BE();
PackSize = input.ReadI32BE();
HorizRes = input.ReadI32BE() >> 16; // read 2 bytes and skip next 2
VertRes = input.ReadI32BE() >> 16;
PixelType = input.ReadI16BE();
BPP = input.ReadI16BE();
CompCount = input.ReadI16BE();
CompSize = input.ReadI16BE();
PlaneBytes = input.ReadI32BE();
Table = input.ReadI32BE();
input.Seek (4, SeekOrigin.Current);
if (BPP <= 0 || BPP > 32 || CompCount <= 0 || CompCount > 4 || CompSize <= 0)
throw new InvalidFormatException();
}
}
internal class PictReader
{
IBinaryStream m_input;
PictMetaData m_info;
public PixelFormat Format { get; private set; }
public BitmapPalette Palette { get; private set; }
public PictReader (IBinaryStream input, PictMetaData info)
{
m_input = input;
m_info = info;
}
bool HasAlpha = false;
byte[] m_buffer;
public byte[] Unpack ()
{
Color[] colormap = null;
Pixmap pixmap = null;
m_input.Position = m_info.DataOffset;
while (m_input.PeekByte() != -1)
{
if ((m_input.Position & 1) != 0)
Skip (1);
int code = m_input.ReadU16BE();
if (0x00FF == code || 0xFFFF == code) // EOF
break;
switch (code)
{
case 0x0000: // NOP
continue;
case 0x0001: // Clip
{
int length = m_input.ReadU16BE();
if (length < 2)
throw new InvalidFormatException();
Skip (length-2);
break;
}
case 0x0090:
case 0x0091:
case 0x0098:
case 0x0099:
case 0x009A:
case 0x009B: // BitsRect
{
int stride = 0;
if (code != 0x9A && code != 0x9B)
stride = m_input.ReadU16BE();
else
Skip (6);
// FIXME we just read the first bitmap and override an existing frame
// TODO place bitmap into frame according to its RECT
m_info.OffsetY = m_input.ReadI16BE();
m_info.OffsetX = m_input.ReadI16BE();
m_info.Height = (uint)(m_input.ReadI16BE() - m_info.OffsetY);
m_info.Width = (uint)(m_input.ReadI16BE() - m_info.OffsetX);
if (0x9A == code || 0x9B == code || (stride & 0x8000) != 0)
{
pixmap = new Pixmap();
pixmap.Deserialize (m_input);
HasAlpha = pixmap.CompCount == 4;
}
if (code != 0x9A && code != 0x9B)
{
int colors = 2;
int flags = 0;
if ((stride & 0x8000) != 0)
{
Skip (4);
flags = m_input.ReadU16BE();
colors = m_input.ReadU16BE() + 1;
}
if (null == colormap)
colormap = new Color[colors];
if ((stride & 0x8000) != 0)
{
for (int i = 0; i < colors; i++)
{
int c = m_input.ReadU16BE() % colors;
if ((flags & 0x8000) != 0)
c = i;
int r = m_input.ReadU16BE() / 0x101;
int g = m_input.ReadU16BE() / 0x101;
int b = m_input.ReadU16BE() / 0x101;
colormap[c] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
}
}
else
{
var White = Color.FromRgb (0xFF, 0xFF, 0xFF);
for (int i = 0; i < colors; i++)
{
colormap[i] = Color.Subtract (White, colormap[i]);
}
}
}
Skip (8+8+2);
// -> Skip (8); // source RECT
// Skip (8); // destination RECT
// Skip (2); // transfer mode
if (code == 0x91 || code == 0x99 || code == 0x9b)
{
int length = m_input.ReadU16BE();
if (length > 2)
Skip (length - 2);
}
if (code != 0x9A && code != 0x9B && (stride & 0x8000) == 0)
DecodeRleBitmap (stride, 1);
else
DecodeRleBitmap (stride, pixmap.BPP);
break;
}
case 0x00A1: // LongComment
{
m_input.ReadU16BE(); // comment type
int length = m_input.ReadU16BE();
Skip (length);
break;
}
case 0x0C00: // Header
Skip (0x18);
break;
default:
throw new NotSupportedException (string.Format ("Unknown code 0x{0:X4} in PICT stream.", code));
}
}
if (colormap != null)
Palette = new BitmapPalette (colormap);
if (null == m_buffer)
throw new InvalidFormatException();
SetFormat (pixmap);
return RepackPixels (pixmap);
}
byte[] RepackPixels (Pixmap pixmap)
{
int bpp = m_info.BPP;
if (bpp <= 16)
return m_buffer;
int bytes_per_pixel = bpp / 8;
int stride = m_info.iWidth * bytes_per_pixel;
var pixels = new byte[stride * m_info.iHeight];
int src = 0;
for (int y = 0; y < m_info.iHeight; ++y)
{
int dst = y * stride;
for (int x = 0; x < m_info.iWidth; ++x)
{
if (HasAlpha)
{
pixels[dst+3] = m_buffer[src];
pixels[dst+2] = m_buffer[src+m_info.iWidth];
pixels[dst+1] = m_buffer[src+m_info.iWidth*2];
pixels[dst] = m_buffer[src+m_info.iWidth*3];
}
else
{
pixels[dst+2] = m_buffer[src];
pixels[dst+1] = m_buffer[src+m_info.iWidth];
pixels[dst] = m_buffer[src+m_info.iWidth*2];
}
++src;
dst += bytes_per_pixel;
}
src += (pixmap.CompCount - 1) * m_info.iWidth;
}
return pixels;
}
void SetFormat (Pixmap pixmap)
{
int bpp = null == pixmap ? 8 : pixmap.BPP;
if (32 == bpp)
{
if (4 == pixmap.CompCount)
Format = PixelFormats.Bgra32;
else
Format = PixelFormats.Bgr32;
}
else if (24 == bpp)
Format = PixelFormats.Bgr24;
else if (16 == bpp)
Format = PixelFormats.Bgr555;
else if (8 == bpp)
{
if (Palette != null)
Format = PixelFormats.Indexed8;
else
Format = PixelFormats.Gray8;
}
else
throw new NotSupportedException (string.Format ("Not supported PICT bitdepth -- {0}bpp", bpp));
m_info.BPP = bpp;
}
void Skip (int amount)
{
m_input.Seek (amount, SeekOrigin.Current);
}
byte[] m_unpack_buffer = new byte[0x800];
byte[] m_scanline;
void DecodeRleBitmap (int stride, int bpp)
{
if (bpp < 8)
throw new NotSupportedException();
if (bpp <= 8)
stride &= 0x7fff;
int width = m_info.iWidth;
int bytes_per_pixel = 1;
if (16 == bpp)
{
bytes_per_pixel = 2;
width *= 2;
}
else if (32 == bpp)
width *= HasAlpha ? 4 : 3;
if (stride == 0)
stride = width;
int stride_32bpp = m_info.iWidth * 4;
int total_bytes = stride_32bpp * m_info.iHeight;
if (null == m_buffer || m_buffer.Length < total_bytes)
m_buffer = new byte[total_bytes];
int scanline_length = stride_32bpp * 2;
if (null == m_scanline || m_scanline.Length < scanline_length)
{
m_scanline = new byte[scanline_length];
}
if (stride < 8)
{
int dst = 0;
int row_size = width * (bpp / 8);
for (int y = 0; y < m_info.iHeight; ++y)
{
m_input.Read (m_buffer, dst, stride);
dst += row_size;
}
return;
}
for (int y = 0; y < m_info.iHeight; ++y)
{
int dst = y * width;
if (stride > 200)
scanline_length = m_input.ReadU16BE();
else
scanline_length = m_input.ReadUInt8();
if (scanline_length >= m_scanline.Length || scanline_length == 0)
throw new InvalidFormatException();
m_input.Read (m_scanline, 0, scanline_length);
for (int j = 0; j < scanline_length; )
{
if ((m_scanline[j] & 0x80) == 0)
{
int pixel_count = m_scanline[j] + 1;
int count = pixel_count * bytes_per_pixel;
int src = j + 1;
if ((dst + count) <= total_bytes)
Buffer.BlockCopy (m_scanline, src, m_buffer, dst, count);
dst += count;
j += count + 1;
}
else
{
int count = (m_scanline[j] ^ 0xFF) + 2;
int src = j + 1;
while (count --> 0)
{
if ((dst + bytes_per_pixel) <= total_bytes)
Buffer.BlockCopy (m_scanline, src, m_buffer, dst, bytes_per_pixel);
dst += bytes_per_pixel;
}
j += bytes_per_pixel + 1;
}
}
}
}
}
}

View File

@ -51,7 +51,7 @@ namespace GameRes.Formats.Macromedia
} }
internal static readonly HashSet<string> RawChunks = new HashSet<string> { internal static readonly HashSet<string> RawChunks = new HashSet<string> {
"RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", "snd "
}; };
internal bool ConvertText = true; internal bool ConvertText = true;
@ -73,9 +73,11 @@ namespace GameRes.Formats.Macromedia
ImportMedia (dir_file, dir); ImportMedia (dir_file, dir);
foreach (MemoryMapEntry entry in dir_file.MMap.Dir) foreach (MemoryMapEntry entry in dir_file.MMap.Dir)
{ {
if (RawChunks.Contains (entry.FourCC)) if (entry.Size != 0 && RawChunks.Contains (entry.FourCC))
{ {
entry.Name = string.Format ("{0:D6}.{1}", entry.Id, entry.FourCC.Trim()); entry.Name = string.Format ("{0:D6}.{1}", entry.Id, entry.FourCC.Trim());
if ("snd " == entry.FourCC)
entry.Type = "audio";
dir.Add (entry); dir.Add (entry);
} }
} }
@ -98,6 +100,8 @@ namespace GameRes.Formats.Macromedia
internal Stream OpenSound (ArcFile arc, SoundEntry entry) internal Stream OpenSound (ArcFile arc, SoundEntry entry)
{ {
if (null == entry.Header)
return base.OpenEntry (arc, entry);
var header = arc.File.View.ReadBytes (entry.Header.Offset, entry.Header.Size); var header = arc.File.View.ReadBytes (entry.Header.Offset, entry.Header.Size);
var format = entry.DeserializeHeader (header); var format = entry.DeserializeHeader (header);
var riff = new MemoryStream (0x2C); var riff = new MemoryStream (0x2C);
@ -147,11 +151,30 @@ namespace GameRes.Formats.Macromedia
Entry ImportSound (CastMember sound, DirectorFile dir_file) Entry ImportSound (CastMember sound, DirectorFile dir_file)
{ {
var sndHrec = dir_file.KeyTable.FindByCast (sound.Id, "sndH"); var name = sound.Info.Name;
var sndSrec = dir_file.KeyTable.FindByCast (sound.Id, "sndS"); KeyTableEntry sndHrec = null, sndSrec = null;
foreach (var elem in dir_file.KeyTable.Table.Where (e => e.CastId == sound.Id))
{
if ("ediM" == elem.FourCC)
{
var ediM = dir_file.MMap[elem.Id];
if (string.IsNullOrEmpty (name))
name = ediM.Id.ToString ("D6");
return new Entry
{
Name = name + ".mp3",
Type = "audio",
Offset = ediM.Offset,
Size = ediM.Size,
};
}
if (null == sndHrec && "sndH" == elem.FourCC)
sndHrec = elem;
else if (null == sndSrec && "sndS" == elem.FourCC)
sndSrec = elem;
}
if (sndHrec == null || sndSrec == null) if (sndHrec == null || sndSrec == null)
return null; return null;
var name = sound.Info.Name;
var sndH = dir_file.MMap[sndHrec.Id]; var sndH = dir_file.MMap[sndHrec.Id];
var sndS = dir_file.MMap[sndSrec.Id]; var sndS = dir_file.MMap[sndSrec.Id];
if (string.IsNullOrEmpty (name)) if (string.IsNullOrEmpty (name))
@ -168,7 +191,15 @@ namespace GameRes.Formats.Macromedia
Entry ImportBitmap (CastMember bitmap, DirectorFile dir_file, Cast cast) Entry ImportBitmap (CastMember bitmap, DirectorFile dir_file, Cast cast)
{ {
var bitd = dir_file.KeyTable.FindByCast (bitmap.Id, "BITD"); // var bitd = dir_file.KeyTable.FindByCast (bitmap.Id, "BITD");
KeyTableEntry bitd = null, alfa = null;
foreach (var elem in dir_file.KeyTable.Table.Where (e => e.CastId == bitmap.Id))
{
if (null == bitd && "BITD" == elem.FourCC)
bitd = elem;
else if (null == alfa && "ALFA" == elem.FourCC)
alfa = elem;
}
if (bitd == null) if (bitd == null)
return null; return null;
var entry = new BitmapEntry(); var entry = new BitmapEntry();
@ -188,6 +219,8 @@ namespace GameRes.Formats.Macromedia
if (clut != null) if (clut != null)
entry.PaletteRef = dir_file.MMap[clut.Id]; entry.PaletteRef = dir_file.MMap[clut.Id];
} }
if (alfa != null)
entry.AlphaRef = dir_file.MMap[alfa.Id];
return entry; return entry;
} }
@ -214,13 +247,27 @@ namespace GameRes.Formats.Macromedia
case -101: palette = Palettes.SystemWindows; break; case -101: palette = Palettes.SystemWindows; break;
} }
} }
var input = arc.File.CreateStream (entry.Offset, entry.Size);
var info = new ImageMetaData { var info = new ImageMetaData {
Width = (uint)(bent.Right - bent.Left), Width = (uint)(bent.Right - bent.Left),
Height = (uint)(bent.Bottom - bent.Top), Height = (uint)(bent.Bottom - bent.Top),
BPP = bent.BitDepth BPP = bent.BitDepth
}; };
return new BitdDecoder (input.AsStream, info, palette); byte[] alpha_channel = null;
if (bent.AlphaRef != null)
{
using (var alpha = arc.File.CreateStream (bent.AlphaRef.Offset, bent.AlphaRef.Size))
{
var alpha_info = new ImageMetaData {
Width = info.Width,
Height = info.Height,
BPP = 8,
};
var decoder = new BitdDecoder (alpha, alpha_info, null);
alpha_channel = decoder.Unpack8bpp();
}
}
var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new BitdDecoder (input.AsStream, info, palette) { AlphaChannel = alpha_channel };
} }
BitmapPalette ReadPalette (byte[] data) BitmapPalette ReadPalette (byte[] data)
@ -246,6 +293,7 @@ namespace GameRes.Formats.Macromedia
public int BitDepth; public int BitDepth;
public int Palette; public int Palette;
public Entry PaletteRef; public Entry PaletteRef;
public Entry AlphaRef;
public void DeserializeHeader (byte[] data) public void DeserializeHeader (byte[] data)
{ {

View File

@ -330,8 +330,8 @@ namespace GameRes.Formats.Macromedia
{ {
int rsize = m_bits.GetBits (5); int rsize = m_bits.GetBits (5);
m_dim.X = GetSignedBits (rsize); m_dim.X = GetSignedBits (rsize);
m_dim.Width = GetSignedBits (rsize) - m_dim.X;
m_dim.Y = GetSignedBits (rsize); m_dim.Y = GetSignedBits (rsize);
m_dim.Width = GetSignedBits (rsize) - m_dim.X;
m_dim.Height = GetSignedBits (rsize) - m_dim.Y; m_dim.Height = GetSignedBits (rsize) - m_dim.Y;
} }

View File

@ -0,0 +1,112 @@
//! \file AudioSND.cs
//! \date 2023 Aug 30
//! \brief Macromedia Director WAVE audio resource.
//
// Copyright (C) 2023 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;
namespace GameRes.Formats.Macromedia
{
[Export(typeof(AudioFormat))]
public class SndAudio : AudioFormat
{
public override string Tag { get => "SND"; }
public override string Description { get => "Macromedia Director audio resource"; }
public override uint Signature { get => 0; }
public override bool CanWrite { get => false; }
public override SoundInput TryOpen (IBinaryStream file)
{
if (!file.Name.HasExtension (".snd"))
return null;
int type = file.ReadUInt16();
if (type != 0x0200)
return null;
var reader = new Reader (file.AsStream, ByteOrder.BigEndian);
reader.Skip (2);
int count = reader.ReadU16();
if (0 == count)
return null;
int command = reader.ReadU16();
if (command != 0x8051)
return null;
reader.ReadI16();
int pos = reader.ReadI32();
if (pos != reader.Position)
return null;
ushort channels = 1;
ushort bps = 8;
reader.Skip (4);
int param = reader.ReadI32();
ushort sample_rate = reader.ReadU16();
reader.Skip (10);
byte encoding = reader.ReadU8();
byte freq = reader.ReadU8();
if (freq != 0x3C)
return null;
int frames_count = 0;
if (0 == encoding)
{
frames_count = param / channels;
}
else if (0xFF == encoding)
{
channels = (ushort)param;
frames_count = reader.ReadI32();
reader.Skip (22);
bps = reader.ReadU16();
reader.Skip (14);
}
else
throw new NotSupportedException (string.Format ("Not supported 'snd' encoding {0:X2}", encoding));
if (bps != 16 && bps != 8)
return null;
var format = new WaveFormat {
FormatTag = 1,
Channels = channels,
SamplesPerSecond = sample_rate,
BlockAlign = (ushort)(bps / 8),
BitsPerSample = bps,
};
format.SetBPS();
if (8 == bps)
{
var data = new StreamRegion (file.AsStream, file.Position);
return new RawPcmInput (data, format);
}
int sample_count = frames_count * channels;
var samples = file.ReadBytes (sample_count);
for (int i = 1; i < samples.Length; i += 2)
{
byte s = samples[i-1];
samples[i-1] = samples[i];
samples[i] = s;
}
var raw = new BinMemoryStream (samples);
file.Dispose();
return new RawPcmInput (raw, format);
}
}
}

View File

@ -215,11 +215,12 @@ namespace GameRes.Formats.Macromedia
ImageData m_image; ImageData m_image;
BitmapPalette m_palette; BitmapPalette m_palette;
public Stream Source { get => m_input; } public Stream Source => m_input;
public ImageFormat SourceFormat { get => null; } public ImageFormat SourceFormat => null;
public ImageMetaData Info { get => m_info; } public ImageMetaData Info => m_info;
public ImageData Image { get => m_image ?? (m_image = GetImageData()); } public ImageData Image => m_image ?? (m_image = GetImageData());
public PixelFormat Format { get; private set; } public PixelFormat Format { get; private set; }
public byte[] AlphaChannel { get; set; }
public BitdDecoder (Stream input, ImageMetaData info, BitmapPalette palette) public BitdDecoder (Stream input, ImageMetaData info, BitmapPalette palette)
{ {
@ -230,7 +231,8 @@ namespace GameRes.Formats.Macromedia
m_stride = (m_width * m_info.BPP + 7) / 8; m_stride = (m_width * m_info.BPP + 7) / 8;
m_stride = (m_stride + 1) & ~1; m_stride = (m_stride + 1) & ~1;
m_output = new byte[m_stride * m_height]; m_output = new byte[m_stride * m_height];
Format = info.BPP == 4 ? PixelFormats.Indexed4 Format = info.BPP == 2 ? PixelFormats.Indexed2
: info.BPP == 4 ? PixelFormats.Indexed4
: info.BPP == 8 ? PixelFormats.Indexed8 : info.BPP == 8 ? PixelFormats.Indexed8
: info.BPP == 16 ? PixelFormats.Bgr555 : info.BPP == 16 ? PixelFormats.Bgr555
: PixelFormats.Bgr32; : PixelFormats.Bgr32;
@ -239,14 +241,46 @@ namespace GameRes.Formats.Macromedia
protected ImageData GetImageData () protected ImageData GetImageData ()
{ {
m_input.Position = 0;
if (Info.BPP <= 8) if (Info.BPP <= 8)
Unpack8bpp(); Unpack8bpp();
else else
UnpackChannels (Info.BPP / 8); UnpackChannels (Info.BPP / 8);
if (AlphaChannel != null)
{
if (Info.BPP != 32)
{
BitmapSource bitmap = BitmapSource.Create (Info.iWidth, Info.iHeight, ImageData.DefaultDpiX, ImageData.DefaultDpiY, Format, m_palette, m_output, m_stride);
bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgr32, null, 0);
m_stride = bitmap.PixelWidth * 4;
m_output = new byte[bitmap.PixelHeight * m_stride];
bitmap.CopyPixels (m_output, m_stride, 0);
}
ApplyAlphaChannel (AlphaChannel);
Format = PixelFormats.Bgra32;
}
return ImageData.Create (m_info, Format, m_palette, m_output, m_stride); return ImageData.Create (m_info, Format, m_palette, m_output, m_stride);
} }
void Unpack8bpp () void ApplyAlphaChannel (byte[] alpha)
{
int alpha_stride = (m_width + 1) & ~1;
int src = 0;
int pdst = 3;
for (int y = 0; y < m_height; ++y)
{
int dst = pdst;
for (int x = 0; x < m_width; ++x)
{
m_output[dst] = alpha[src+x];
dst += 4;
}
src += alpha_stride;
pdst += m_stride;
}
}
public byte[] Unpack8bpp ()
{ {
for (int line = 0; line < m_output.Length; line += m_stride) for (int line = 0; line < m_output.Length; line += m_stride)
{ {
@ -277,6 +311,7 @@ namespace GameRes.Formats.Macromedia
} }
} }
} }
return m_output;
} }
public void UnpackChannels (int channels) public void UnpackChannels (int channels)

94
ArcFormats/Mugi/ArcBIN.cs Normal file
View File

@ -0,0 +1,94 @@
//! \file ArcBIN.cs
//! \date 2023 Sep 03
//! \brief Mugi's resource archive.
//
// Copyright (C) 2023 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 GameRes.Compression;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
// [070323][schoolzone] Cosplay! Kyonyuu Mahjong
namespace GameRes.Formats.Mugi
{
[Export(typeof(ArchiveFormat))]
public class BinOpener : ArchiveFormat
{
public override string Tag { get => "BIN/MUGI"; }
public override string Description { get => "Mugi's resource archive"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => false; }
public override bool CanWrite { get => false; }
public override ArcFile TryOpen (ArcView file)
{
const uint index_size = 0x8000 + 0x4000;
if (file.MaxOffset <= index_size)
return null;
file.View.Reserve (0, index_size);
uint index_pos = 0x8000;
uint offset = file.View.ReadUInt32 (index_pos);
if (offset != index_size)
return null;
uint[] offsets = new uint[0x800];
int count = 0;
while (offset != file.MaxOffset)
{
if (count == offsets.Length)
return null;
offsets[count++] = offset;
index_pos += 4;
offset = file.View.ReadUInt32 (index_pos);
if (offset < offsets[count-1] || offset > file.MaxOffset)
return null;
}
offsets[count--] = offset;
uint name_pos = 0;
uint size_pos = 0xA000;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (name_pos, 0x10);
var entry = Create<PackedEntry> (name);
entry.Offset = offsets[i];
entry.Size = (uint)(offsets[i+1] - offsets[i]);
entry.UnpackedSize = file.View.ReadUInt32 (size_pos);
entry.IsPacked = entry.Size != entry.UnpackedSize;
dir.Add (entry);
name_pos += 0x10;
size_pos += 4;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
Stream input = arc.File.CreateStream (entry.Offset, entry.Size);
if (pent.IsPacked)
input = new LzssStream (input);
return input;
}
}
}

View File

@ -126,16 +126,32 @@ namespace GameRes.Formats.Qlie
} }
var enc = QlieEncryption.Create (file, index.PackVersion, arc_key); var enc = QlieEncryption.Create (file, index.PackVersion, arc_key);
List<Entry> dir = null; List<Entry> dir = null;
try if (index.PackVersion.Major > 1)
{ {
dir = index.Read (enc, key_file, use_pack_keyfile); dir = index.Read (enc, key_file, use_pack_keyfile);
} }
catch else
{ {
if (index.PackVersion.Major == 1) // PackVer1.0 is a total mess -- it could either use
// • V1 index layout and V1 encryption
// • V1 index layout and V2 encryption
// • V2 index layout and V2 encryption
// all with the same 'FilePackVer1.0' signature
var possibleEncs = new IEncryption[] {
enc, new EncryptionV2 (IndexLayout.WithoutHash), new EncryptionV2()
};
foreach (var v1enc in possibleEncs)
{ {
enc = new EncryptionV2(); try
dir = index.Read (enc, key_file, use_pack_keyfile); {
dir = index.Read (v1enc, key_file, use_pack_keyfile);
if (dir != null)
{
enc = v1enc;
break;
}
}
catch { }
} }
} }
if (null == dir) if (null == dir)
@ -400,6 +416,8 @@ namespace GameRes.Formats.Qlie
for (int i = 0; i < m_count; ++i) for (int i = 0; i < m_count; ++i)
{ {
int name_length = m_index.ReadUInt16(); int name_length = m_index.ReadUInt16();
if (name_length > 0x100) // invalid encryption version
return null;
if (enc.IsUnicode) if (enc.IsUnicode)
name_length *= 2; name_length *= 2;
if (name_length > m_name_buffer.Length) if (name_length > m_name_buffer.Length)
@ -418,8 +436,8 @@ namespace GameRes.Formats.Qlie
entry.UnpackedSize = m_index.ReadUInt32(); // [+0C] entry.UnpackedSize = m_index.ReadUInt32(); // [+0C]
entry.IsPacked = 0 != m_index.ReadInt32(); // [+10] entry.IsPacked = 0 != m_index.ReadInt32(); // [+10]
entry.EncryptionMethod = m_index.ReadInt32(); // [+14] entry.EncryptionMethod = m_index.ReadInt32(); // [+14]
if (m_pack_version.Major > 1) if (enc.IndexLayout == IndexLayout.WithHash)
entry.Hash = m_index.ReadUInt32(); // [+18] entry.Hash = m_index.ReadUInt32(); // [+18]
entry.KeyFile = key_file; entry.KeyFile = key_file;
if (read_pack_keyfile && entry.Name.Contains ("pack_keyfile")) if (read_pack_keyfile && entry.Name.Contains ("pack_keyfile"))
{ {

View File

@ -33,6 +33,8 @@ namespace GameRes.Formats.Qlie
{ {
bool IsUnicode { get; } bool IsUnicode { get; }
IndexLayout IndexLayout { get; }
uint CalculateHash (byte[] data, int length); uint CalculateHash (byte[] data, int length);
string DecryptName (byte[] name, int name_length); string DecryptName (byte[] name, int name_length);
@ -40,9 +42,17 @@ namespace GameRes.Formats.Qlie
void DecryptEntry (byte[] data, int offset, int length, QlieEntry entry); void DecryptEntry (byte[] data, int offset, int length, QlieEntry entry);
} }
internal enum IndexLayout
{
WithoutHash,
WithHash
}
internal abstract class QlieEncryption : IEncryption internal abstract class QlieEncryption : IEncryption
{ {
public virtual bool IsUnicode { get { return false; } } public virtual bool IsUnicode => false;
public virtual IndexLayout IndexLayout => IndexLayout.WithHash;
/// <summary> /// <summary>
/// Hash generated from the key data contained within archive index. /// Hash generated from the key data contained within archive index.
@ -77,6 +87,8 @@ namespace GameRes.Formats.Qlie
internal class EncryptionV1 : QlieEncryption internal class EncryptionV1 : QlieEncryption
{ {
public override IndexLayout IndexLayout => IndexLayout.WithoutHash;
public EncryptionV1 () public EncryptionV1 ()
{ {
NameKey = 0xC4; NameKey = 0xC4;
@ -126,10 +138,15 @@ namespace GameRes.Formats.Qlie
internal class EncryptionV2 : QlieEncryption internal class EncryptionV2 : QlieEncryption
{ {
public EncryptionV2 () public override IndexLayout IndexLayout => m_layout;
private IndexLayout m_layout;
public EncryptionV2 (IndexLayout layout = IndexLayout.WithHash)
{ {
NameKey = 0xC4; NameKey = 0xC4;
ArcKey = 0; ArcKey = 0;
m_layout = layout;
} }
public override uint CalculateHash (byte[] data, int length) public override uint CalculateHash (byte[] data, int length)

View File

@ -278,11 +278,11 @@ namespace GameRes.Formats.ScrPlayer
#endregion #endregion
#region Bitmap tables #region Bitmap tables
static readonly Lazy<byte[]> s_control_table1 = new Lazy<byte[]> (() => LoadResource ("ControlTable1")); static readonly Lazy<byte[]> s_control_table1 = new Lazy<byte[]> (() => LoadResource ("IControlTable1"));
static readonly Lazy<byte[]> s_control_table2 = new Lazy<byte[]> (() => LoadResource ("ControlTable2")); static readonly Lazy<byte[]> s_control_table2 = new Lazy<byte[]> (() => LoadResource ("IControlTable2"));
static readonly Lazy<byte[]> s_control_table32 = new Lazy<byte[]> (() => LoadResource ("ControlTable32")); static readonly Lazy<byte[]> s_control_table32 = new Lazy<byte[]> (() => LoadResource ("IControlTable32"));
static readonly Lazy<byte[]> s_color_bits1 = new Lazy<byte[]> (() => LoadResource ("ColorBitsTable1")); static readonly Lazy<byte[]> s_color_bits1 = new Lazy<byte[]> (() => LoadResource ("IColorBitsTable1"));
static readonly Lazy<byte[]> s_color_bits2 = new Lazy<byte[]> (() => LoadResource ("ColorBitsTable2")); static readonly Lazy<byte[]> s_color_bits2 = new Lazy<byte[]> (() => LoadResource ("IColorBitsTable2"));
static byte[] ControlTable1 { get { return s_control_table1.Value; } } static byte[] ControlTable1 { get { return s_control_table1.Value; } }
static byte[] ControlTable2 { get { return s_control_table2.Value; } } static byte[] ControlTable2 { get { return s_control_table2.Value; } }
@ -390,7 +390,7 @@ namespace GameRes.Formats.ScrPlayer
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
} }
}; };
static readonly int[] OffsetTable = { internal static readonly int[] OffsetTable = {
-1, 0, 0, 1, 1, 1, -1, 1, 2, 1, -2, 1, -2, 0, 0, 2, 1, 2, -1, 2, -3, 0, -1, 0, 0, 1, 1, 1, -1, 1, 2, 1, -2, 1, -2, 0, 0, 2, 1, 2, -1, 2, -3, 0,
}; };
#endregion #endregion

View File

@ -0,0 +1,490 @@
//! \file ImageIMG.cs
//! \date 2023 Aug 30
//! \brief ScrPlayer image format.
//
// Copyright (C) 2023 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 GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
// [990709][Love Gun] Koi no Sweet Tart wa Ikaga
namespace GameRes.Formats.ScrPlayer
{
[Export(typeof(ImageFormat))]
public class ImgFormat : ImageFormat
{
public override string Tag { get => "IMG"; }
public override string Description { get => "ScrPlayer image format"; }
public override uint Signature { get => 0x20474D49; } // 'IMG '
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x18);
int bpp = header.ToUInt16 (0x10);
if (bpp != 24 && bpp != 32)
return null;
return new ImageMetaData {
Width = header.ToUInt16 (0xC),
Height = header.ToUInt16 (0xE),
BPP = bpp,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
using (var reader = new ImgReader (file, info))
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("ImgFormat.Write not implemented");
}
}
internal sealed class ImgReader : IDisposable
{
ImgBitStream m_input;
ImageMetaData m_info;
int m_stride;
byte[] m_output;
public PixelFormat Format { get; private set; }
public ImgReader (IBinaryStream input, ImageMetaData info)
{
m_input = new ImgBitStream (input.AsStream, true);
m_info = info;
m_stride = m_info.iWidth * 4;
m_output = new byte[m_stride * (m_info.iHeight + 1)];
Format = m_info.BPP == 32 ? PixelFormats.Bgra32 : PixelFormats.Bgr32;
}
public ImageData Unpack ()
{
m_input.Input.Position = 0x18;
if (32 == m_info.BPP)
Unpack32bpp();
else
Unpack24bpp();
return ImageData.Create (m_info, Format, null, m_output);
}
byte[] m_row_buffer = new byte[0x2580];
int[] m_rows_ptr = new int[3] { 0, 0xC80, 0xC80 * 2 };
void Unpack24bpp ()
{
var offset_table = Img2Reader.OffsetTable.Clone() as int[];
int dst = 0;
for (int y = 0; y < m_info.iHeight; ++y)
{
int row_pos = m_rows_ptr[2];
m_rows_ptr[2] = m_rows_ptr[1];
m_rows_ptr[1] = m_rows_ptr[0];
m_rows_ptr[0] = row_pos;
for (int x = 0; x < m_info.iWidth; )
{
int ctl = m_input.GetBits (ControlTable1, 13);
if (0xEF == ctl)
ctl += m_input.GetBits (ControlTable2, 11);
int pos = m_input.GetBits (PosTable24, 5) * 2;
int x_offset = offset_table[pos];
int y_offset = offset_table[pos + 1];
if (pos > 0)
{
offset_table[pos] = offset_table[pos-2];
offset_table[pos+1] = offset_table[pos-1];
offset_table[pos-2] = x_offset;
offset_table[pos-1] = y_offset;
}
int src = m_rows_ptr[y_offset] + (x + x_offset) * 4;
if (ctl >= 0xD8)
{
int count = ctl - 0xD6;
Binary.CopyOverlapped (m_row_buffer, src, row_pos, count * 4);
row_pos += count * 4;
x += count;
}
else
{
switch (ColorCode[ctl])
{
case 0:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]);
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]);
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]);
break;
case 1:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta());
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]);
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]);
break;
case 2:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]);
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta());
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]);
break;
case 3:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta());
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta());
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]);
break;
case 4:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]);
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]);
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta());
break;
case 5:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta());
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]);
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta());
break;
case 6:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]);
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta());
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta());
break;
case 7:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta());
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta());
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta());
break;
}
row_pos += 4;
++x;
}
}
Buffer.BlockCopy (m_row_buffer, m_rows_ptr[0], m_output, dst, m_stride);
dst += m_stride;
}
}
void Unpack32bpp ()
{
for (int i = 3; i < m_row_buffer.Length; i += 4)
m_row_buffer[i] = 0xFF;
var offset_table = Img2Reader.OffsetTable.Clone() as int[];
int dst = 0;
for (int y = 0; y < m_info.iHeight; ++y)
{
int row_pos = m_rows_ptr[2];
m_rows_ptr[2] = m_rows_ptr[1];
m_rows_ptr[1] = m_rows_ptr[0];
m_rows_ptr[0] = row_pos;
for (int x = 0; x < m_info.iWidth; )
{
int ctl = m_input.GetBits (ControlTable1, 13);
if (0xEF == ctl)
ctl += m_input.GetBits (ControlTable2, 11);
int t = m_input.GetBits (ControlTable32, 13) * 2;
int pos = PosTable32[t] * 2;
int alpha = PosTable32[t+1];
int x_offset = offset_table[pos];
int y_offset = offset_table[pos + 1];
if (pos > 0)
{
offset_table[pos] = offset_table[pos-2];
offset_table[pos+1] = offset_table[pos-1];
offset_table[pos-2] = x_offset;
offset_table[pos-1] = y_offset;
}
int src = m_rows_ptr[y_offset] + (x + x_offset) * 4;
if (ctl >= 0xD8)
{
int count = ctl - 0xD6;
Binary.CopyOverlapped (m_row_buffer, src, row_pos, count * 4);
row_pos += count * 4;
x += count;
}
else
{
switch (ColorCode[ctl])
{
case 0:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]);
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]);
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]);
break;
case 1:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta());
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]);
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]);
break;
case 2:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]);
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta());
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]);
break;
case 3:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta());
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta());
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]);
break;
case 4:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]);
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]);
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta());
break;
case 5:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta());
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]);
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta());
break;
case 6:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]);
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta());
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta());
break;
case 7:
m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta());
m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta());
m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta());
break;
}
if (-3 == alpha)
{
alpha = GetDelta();
}
m_row_buffer[row_pos + 3] = (byte)(m_row_buffer[src + 3] - alpha);
row_pos += 4;
++x;
}
}
Buffer.BlockCopy (m_row_buffer, m_rows_ptr[0], m_output, dst, m_stride);
dst += m_stride;
}
}
int GetDelta ()
{
int d = m_input.GetBits (DeltaTable1, 8);
if (0x2B == d)
d += m_input.GetBits (DeltaTable2, 13);
return DeltaTable3[d];
}
static byte[] LoadResource (string name) => EmbeddedResource.Load (name, typeof(ImgReader));
#region IDisposable Members
bool _disposed = false;
public void Dispose ()
{
if (!_disposed)
{
m_input.Dispose();
_disposed = true;
}
}
#endregion
#region Bitmap Tables
static readonly Lazy<byte[]> s_control_table1 = new Lazy<byte[]> (() => LoadResource ("ImgControlTable1"));
static readonly Lazy<byte[]> s_control_table2 = new Lazy<byte[]> (() => LoadResource ("ImgControlTable2"));
static readonly Lazy<byte[]> s_control_table32 = new Lazy<byte[]> (() => LoadResource ("ImgControlTable32"));
static readonly Lazy<byte[]> s_delta_table2 = new Lazy<byte[]> (() => LoadResource ("ImgDeltaTable2"));
static byte[] ControlTable1 { get { return s_control_table1.Value; } }
static byte[] ControlTable2 { get { return s_control_table2.Value; } }
static byte[] ControlTable32 { get { return s_control_table32.Value; } }
static byte[] DeltaTable2 { get { return s_delta_table2.Value; } }
static readonly byte[] PosTable24 = {
4, 2, 1, 0, 4, 3, 1, 0, 3, 1, 1, 0, 4, 4, 1, 0, 5, 5, 1, 0, 5, 7, 1, 0, 3, 1, 1, 0, 5, 9, 1, 0, 4,
2, 1, 0, 4, 3, 1, 0, 3, 1, 1, 0, 4, 4, 1, 0, 5, 6, 1, 0, 5, 8, 1, 0, 3, 1, 1, 0, 5, 10, 1, 0,
};
static readonly sbyte[] PosTable32 = {
0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0, 0, -3, 1, -3, 2, -3, 3, -3, 4,
-3, 5, -3, 6, -3, 7, -3, 8, -3, 9, -3, 10, -3, 0, -1, 1, -1, 2, -1, 3, -1, 4, -1, 5, -1, 6, -1, 7,
-1, 8, -1, 9, -1, 10, -1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1, 0,
-2, 1, -2, 2, -2, 3, -2, 4, -2, 5, -2, 6, -2, 7, -2, 8, -2, 9, -2, 10, -2, 0, 2, 1, 2, 2, 2, 3, 2,
4, 2, 5, 2, 6, 2, 7, 2, 8, 2, 9, 2, 10, 2,
};
static readonly byte[] ColorCode = {
7, 3, 3, 3, 3, 3, 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1,
5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0,
4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0,
6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0,
4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0,
4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0,
6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0,
4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0,
4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0,
};
static readonly byte[] DeltaTable1 = {
7, 0x13, 3, 0, 6, 0x0D, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x10, 4, 3, 3, 1, 5,
9, 5, 0x0B, 5, 6, 3, 0, 6, 0x0F, 4, 4, 6, 0x0C, 3, 1, 8, 0x23, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x11,
4, 3, 3, 1, 5, 0x0A, 8, 0x27, 7, 0x15, 3, 0, 6, 0x0E, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4,
0x2B, 7, 0x1B, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 7, 0x18, 4, 4, 8, 0x22, 3, 1, 7, 0x19, 4, 5,
4, 2, 3, 0, 4, 0x2B, 6, 0x12, 4, 3, 3, 1, 5, 0x0A, 6, 0x14, 8, 0x1D, 3, 0, 6, 0x0D, 4, 4, 5, 7, 3,
1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x10, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 6, 0x0F, 4, 4,
6, 0x0C, 3, 1, 7, 0x1A, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x11, 4, 3, 3, 1, 5, 0x0A, 7, 0x1C, 8, 0x20,
3, 0, 6, 0x0E, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 8, 0x28, 4, 3, 3, 1, 5, 9, 5,
0x0B, 5, 6, 3, 0, 7, 0x17, 4, 4, 7, 0x16, 3, 1, 8, 0x26, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x12, 4, 3,
3, 1, 5, 0x0A, 6, 0x14, 7, 0x13, 3, 0, 6, 0x0D, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B,
6, 0x10, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 6, 0x0F, 4, 4, 6, 0x0C, 3, 1, 8, 0x24, 4, 5, 4, 2,
3, 0, 4, 0x2B, 6, 0x11, 4, 3, 3, 1, 5, 0x0A, 8, 0x29, 7, 0x15, 3, 0, 6, 0x0E, 4, 4, 5, 7, 3, 1, 5,
8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 7, 0x1B, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 7, 0x18, 4, 4, 8,
0x21, 3, 1, 7, 0x19, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x12, 4, 3, 3, 1, 5, 0x0A, 6, 0x14, 8, 0x1E, 3,
0, 6, 0x0D, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x10, 4, 3, 3, 1, 5, 9, 5, 0x0B,
5, 6, 3, 0, 6, 0x0F, 4, 4, 6, 0x0C, 3, 1, 7, 0x1A, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x11, 4, 3, 3, 1,
5, 0x0A, 7, 0x1C, 8, 0x1F, 3, 0, 6, 0x0E, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 8,
0x2A, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 7, 0x17, 4, 4, 7, 0x16, 3, 1, 8, 0x25, 4, 5, 4, 2, 3,
0, 4, 0x2B, 6, 0x12, 4, 3, 3, 1, 5, 0x0A, 6, 0x14,
};
static readonly byte[] DeltaTable3 = {
0x03, 0xFD, 0x04, 0xFC, 0x05, 0xFB, 0x06, 0xFA, 0x07, 0xF9, 0x08, 0xF8, 0x09, 0xF7, 0x0A, 0xF6,
0x0B, 0xF5, 0x0C, 0xF4, 0x0D, 0xF3, 0x0E, 0xF2, 0x0F, 0xF1, 0x10, 0xF0, 0x11, 0xEF, 0x12, 0xEE,
0x13, 0xED, 0x14, 0xEC, 0x15, 0xEB, 0x16, 0xEA, 0x17, 0xE9, 0x18, 0xE8, 0x19, 0xE7, 0x1A, 0xE6,
0x1B, 0xE5, 0x1C, 0xE4, 0x1D, 0xE3, 0x1E, 0xE2, 0x1F, 0xE1, 0x20, 0xE0, 0x21, 0xDF, 0x22, 0xDE,
0x23, 0xDD, 0x24, 0xDC, 0x25, 0xDB, 0x26, 0xDA, 0x27, 0xD9, 0x28, 0xD8, 0x29, 0xD7, 0x2A, 0xD6,
0x2B, 0xD5, 0x2C, 0xD4, 0x2D, 0xD3, 0x2E, 0xD2, 0x2F, 0xD1, 0x30, 0xD0, 0x31, 0xCF, 0x32, 0xCE,
0x33, 0xCD, 0x34, 0xCC, 0x35, 0xCB, 0x36, 0xCA, 0x37, 0xC9, 0x38, 0xC8, 0x39, 0xC7, 0x3A, 0xC6,
0x3B, 0xC5, 0x3C, 0xC4, 0x3D, 0xC3, 0x3E, 0xC2, 0x3F, 0xC1, 0x40, 0xC0, 0x41, 0xBF, 0x42, 0xBE,
0x43, 0xBD, 0x44, 0xBC, 0x45, 0xBB, 0x46, 0xBA, 0x47, 0xB9, 0x48, 0xB8, 0x49, 0xB7, 0x4A, 0xB6,
0x4B, 0xB5, 0x4C, 0xB4, 0x4D, 0xB3, 0x4E, 0xB2, 0x4F, 0xB1, 0x50, 0xB0, 0x51, 0xAF, 0x52, 0xAE,
0x53, 0xAD, 0x54, 0xAC, 0x55, 0xAB, 0x56, 0xAA, 0x57, 0xA9, 0x58, 0xA8, 0x59, 0xA7, 0x5A, 0xA6,
0x5B, 0xA5, 0x5C, 0xA4, 0x5D, 0xA3, 0x5E, 0xA2, 0x5F, 0xA1, 0x60, 0xA0, 0x61, 0x9F, 0x62, 0x9E,
0x63, 0x9D, 0x64, 0x9C, 0x65, 0x9B, 0x66, 0x9A, 0x67, 0x99, 0x68, 0x98, 0x69, 0x97, 0x6A, 0x96,
0x6B, 0x95, 0x6C, 0x94, 0x6D, 0x93, 0x6E, 0x92, 0x6F, 0x91, 0x70, 0x90, 0x71, 0x8F, 0x72, 0x8E,
0x73, 0x8D, 0x74, 0x8C, 0x75, 0x8B, 0x76, 0x8A, 0x77, 0x89, 0x78, 0x88, 0x79, 0x87, 0x7A, 0x86,
0x7B, 0x85, 0x7C, 0x84, 0x7D, 0x83, 0x7E, 0x82, 0x7F, 0x81, 0x80, 0,
};
static readonly byte[] MapRed = {
0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD,
0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD,
0xFD, 0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
};
static readonly byte[] MapGreen = {
0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF,
0x0FF, 0x0FF, 0x0FF, 0x0FF, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD,
0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF,
0x0FF, 0x0FF, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD, 0x0FD, 0x0FD,
0x0FD, 0x0FD, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD,
0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FE, 0x0FE,
0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FE, 0x0FE, 0x0FE, 0x0FE,
0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,
2, 2, 2, 2,
};
static readonly byte[] MapBlue = {
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2,
};
#endregion
}
internal class ImgBitStream : BitStream
{
public ImgBitStream (Stream file, bool leave_open = false) : base (file, leave_open)
{
}
public int GetBits (byte[] table, int count)
{
int n = PeekBits (count) * 2;
count = table[n];
m_bits >>= count;
m_cached_bits -= count;
return table[n + 1];
}
public int PeekBits (int count)
{
if (m_cached_bits < count)
{
int b = m_input.ReadByte();
if (-1 == b)
b = 0; // throw new EndOfStreamException();
m_bits |= ByteMap[b] << m_cached_bits;
m_cached_bits += 8;
if (m_cached_bits < count)
{
b = m_input.ReadByte();
if (-1 == b)
b = 0; // throw new EndOfStreamException();
m_bits |= ByteMap[b] << m_cached_bits;
m_cached_bits += 8;
}
}
return m_bits & g_bitMask[count];
}
static readonly int[] g_bitMask = {
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F,
0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF,
0x0000FFFF, 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF, 0x001FFFFF, 0x003FFFFF, 0x007FFFFF,
0x00FFFFFF, 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF,
};
internal static readonly byte[] ByteMap = {
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF,
};
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,63 @@
//! \file ArcScn.cs
//! \date 2023 Aug 28
//! \brief Software House Parsley scripts archive.
//
// Copyright (C) 2023 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.Collections.Generic;
using System.ComponentModel.Composition;
namespace GameRes.Formats.Parsley
{
[Export(typeof(ArchiveFormat))]
public class ScnDatOpener : ArchiveFormat
{
public override string Tag { get => "DAT/SCN"; }
public override string Description { get => "Software House Parsley scenario archive"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => false; }
public override bool CanWrite { get => false; }
public override ArcFile TryOpen (ArcView file)
{
if (!VFS.IsPathEqualsToFileName (file.Name, "scn.dat"))
return null;
uint base_offset = 0x1400;
uint index_pos = 0;
var dir = new List<Entry>();
while (index_pos < base_offset && file.View.ReadByte (index_pos) != 0)
{
var name = file.View.ReadString (index_pos, 0x20);
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_pos+0x20) + base_offset;
entry.Size = file.View.ReadUInt32 (index_pos+0x24);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_pos += 0x28;
}
if (dir.Count == 0)
return null;
return new ArcFile (file, this, dir);
}
}
}

View File

@ -0,0 +1,87 @@
//! \file ScriptDSM.cs
//! \date 2023 Sep 03
//! \brief Decrypt data.dsm script.
//
// Copyright (C) 2023 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.Security.Cryptography;
using System.Text;
namespace GameRes.Formats.NScripter
{
[Export(typeof(ScriptFormat))]
public class DsmConverter : GenericScriptFormat
{
public override string Tag { get => "DSM/UTAGE"; }
public override string Description { get => "UTAGE Unity engine script file"; }
public override uint Signature { get => 0; }
public override bool IsScript (IBinaryStream file)
{
return VFS.IsPathEqualsToFileName (file.Name, "data.dsm");
}
public override Stream ConvertFrom (IBinaryStream file)
{
using (var reader = new StreamReader (file.AsStream))
{
var sourceString = reader.ReadToEnd();
var text = DsmDecryptor.DecryptString (sourceString, "pass");
return new BinMemoryStream (text, file.Name);
}
}
public override Stream ConvertBack (IBinaryStream file)
{
throw new NotSupportedException();
}
}
internal static class DsmDecryptor
{
internal static void GenerateKeyFromPassword (string password, int keySize, out byte[] key, int blockSize, out byte[] iv)
{
var bytes = Encoding.UTF8.GetBytes ("saltは必ず8バイト以上");
var derive = new Rfc2898DeriveBytes (password, bytes);
derive.IterationCount = 1000;
key = derive.GetBytes (keySize / 8);
iv = derive.GetBytes (blockSize / 8);
}
internal static byte[] DecryptString (string sourceString, string password)
{
var rij = new RijndaelManaged();
byte[] key, iv;
GenerateKeyFromPassword (password, rij.KeySize, out key, rij.BlockSize, out iv);
rij.Key = key;
rij.IV = iv;
var array = Convert.FromBase64String (sourceString);
using (var cryptoTransform = rij.CreateDecryptor())
{
return cryptoTransform.TransformFinalBlock (array, 0, array.Length);
}
}
}
}

View File

@ -150,6 +150,8 @@
<Compile Include="Cabinet\ArcCAB.cs" /> <Compile Include="Cabinet\ArcCAB.cs" />
<Compile Include="CellWorks\ArcDB.cs" /> <Compile Include="CellWorks\ArcDB.cs" />
<Compile Include="Artemis\GplexBuffers.cs" /> <Compile Include="Artemis\GplexBuffers.cs" />
<Compile Include="Microsoft\ArcEXE.cs" />
<Compile Include="Microsoft\ArcNE.cs" />
<Compile Include="Opus\AudioOPUS.cs" /> <Compile Include="Opus\AudioOPUS.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RPGMaker\ArcRGSS.cs" /> <Compile Include="RPGMaker\ArcRGSS.cs" />

View File

@ -0,0 +1,184 @@
//! \file ArcEXE.cs
//! \date 2023 Aug 24
//! \brief Access portable executable (PE) resources.
//
// Copyright (C) 2023 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 GameRes.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
namespace GameRes.Formats.Microsoft
{
[Export(typeof(ArchiveFormat))]
public class ExeOpener : ArchiveFormat
{
public override string Tag { get => "EXE"; }
public override string Description { get => "Windows executable resources"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => true; }
public override bool CanWrite { get => false; }
public ExeOpener ()
{
Extensions = new[] { "exe",/* "dll"*/ };
}
static readonly Dictionary<string, string> RuntimeTypeMap = new Dictionary<string, string>() {
{ "#2", "RT_BITMAP" },
{ "#10", "RT_RCDATA" },
};
static readonly Dictionary<string, string> ExtensionTypeMap = new Dictionary<string, string>() {
{ "PNG", ".PNG" },
{ "WAVE", ".WAV" },
{ "MIDS", ".MID" },
{ "SCR", ".BIN" },
{ "#2", ".BMP" },
{ "#10", ".BIN" },
};
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (0, "MZ") || VFS.IsVirtual)
return null;
var res = new ExeFile.ResourceAccessor (file.Name);
try
{
var dir = new List<Entry>();
foreach (var type in res.EnumTypes())
{
string dir_name = type;
if (type.StartsWith ("#") && !RuntimeTypeMap.TryGetValue (type, out dir_name))
continue;
string ext;
if (!ExtensionTypeMap.TryGetValue (type, out ext))
ext = "";
foreach (var name in res.EnumNames (type))
{
string full_name = name;
if (name.StartsWith ("#"))
full_name = IdToString (name);
full_name = string.Join ("/", dir_name, full_name) + ext;
var entry = Create<ResourceEntry> (full_name);
entry.NativeName = name;
entry.NativeType = type;
entry.Offset = 0;
entry.Size = res.GetResourceSize (name, type);
dir.Add (entry);
}
}
if (0 == dir.Count)
{
res.Dispose();
return null;
}
return new ResourcesArchive (file, this, dir, res);
}
catch
{
res.Dispose();
throw;
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var rarc = (ResourcesArchive)arc;
var rent = (ResourceEntry)entry;
var data = rarc.Accessor.GetResource (rent.NativeName, rent.NativeType);
if (null == data)
return Stream.Null;
return new BinMemoryStream (data, rent.Name);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var rent = (ResourceEntry)entry;
if (rent.NativeType != "#2")
return base.OpenImage (arc, entry);
var rarc = (ResourcesArchive)arc;
var bitmap = new byte[14 + entry.Size];
int length = rarc.Accessor.ReadResource (rent.NativeName, rent.NativeType, bitmap, 14);
length += 14;
bitmap[0] = (byte)'B';
bitmap[1] = (byte)'M';
LittleEndian.Pack (length, bitmap, 2);
int bits_length = bitmap.ToInt32 (0x22);
int bits_pos = length - bits_length;
if (bits_length == 0)
bits_pos = bitmap.ToInt32 (14) + 22;
LittleEndian.Pack (bits_pos, bitmap, 10);
var bm = new BinMemoryStream (bitmap, 0, length, entry.Name);
var info = ImageFormat.Bmp.ReadMetaData (bm);
if (null == info)
{
bm.Dispose();
throw new InvalidFormatException ("Invalid bitmap resource.");
}
bm.Position = 0;
return new ImageFormatDecoder (bm, ImageFormat.Bmp, info);
}
internal static string IdToString (string id)
{
if (id.Length > 1 && id[0] == '#' && char.IsDigit (id[1]))
id = id.Substring (1).PadLeft (5, '0');
return id;
}
}
internal class ResourceEntry : Entry
{
public string NativeName;
public string NativeType;
}
internal class ResourcesArchive : ArcFile
{
public readonly ExeFile.ResourceAccessor Accessor;
public ResourcesArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, ExeFile.ResourceAccessor acc)
: base (arc, impl, dir)
{
Accessor = acc;
}
#region IDisposable Members
bool _acc_disposed = false;
protected override void Dispose (bool disposing)
{
if (_acc_disposed)
return;
if (disposing)
Accessor.Dispose();
_acc_disposed = true;
base.Dispose (disposing);
}
#endregion
}
}

View File

@ -0,0 +1,103 @@
//! \file ArcNE.cs
//! \date 2023 Aug 29
//! \brief Access 16-bit NE executable resources.
//
// Copyright (C) 2023 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.IO;
namespace GameRes.Formats.Microsoft
{
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag { get => "EXE/NE"; }
public override string Description { get => "Windows 16-bit executable resources"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => true; }
public override bool CanWrite { get => false; }
static readonly Dictionary<int, string> TypeMap = new Dictionary<int, string> {
{ 1, "RT_CURSOR" },
{ 2, "RT_BITMAP" },
{ 3, "RT_ICON" },
{ 4, "RT_MENU" },
{ 5, "RT_DIALOG" },
{ 6, "RT_STRING" },
{ 10, "RT_DATA" },
{ 11, "RT_MESSAGETABLE" },
{ 16, "RT_VERSION" },
};
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (0, "MZ"))
return null;
uint ne_offset = file.View.ReadUInt32 (0x3C);
if (!file.View.AsciiEqual (ne_offset, "NE"))
return null;
uint res_table_offset = file.View.ReadUInt16 (ne_offset+0x24) + ne_offset;
if (res_table_offset <= ne_offset || res_table_offset >= file.MaxOffset)
return null;
int shift = file.View.ReadUInt16 (res_table_offset);
res_table_offset += 2;
var dir = new List<Entry>();
while (res_table_offset + 1 < file.MaxOffset)
{
int type_id = file.View.ReadUInt16 (res_table_offset);
if (0 == type_id)
break;
string dir_name = null;
if ((type_id & 0x8000) != 0)
{
type_id &= 0x7FFF;
TypeMap.TryGetValue (type_id, out dir_name);
}
int count = file.View.ReadUInt16 (res_table_offset+2);
res_table_offset += 8;
if (null == dir_name)
dir_name = string.Format ("#{0}", type_id);
for (int i = 0; i < count; ++i)
{
int offset = file.View.ReadUInt16 (res_table_offset) << shift;
uint size = (uint)file.View.ReadUInt16 (res_table_offset+2) << shift;
int res_id = file.View.ReadUInt16 (res_table_offset+6);
res_table_offset += 12;
string name = res_id.ToString ("D5");
name = string.Join ("/", dir_name, name);
var entry = new Entry {
Name = name,
Offset = offset,
Size = size,
};
dir.Add (entry);
}
}
if (0 == dir.Count)
return null;
return new ArcFile (file, this, dir);
}
}
}

View File

@ -30,6 +30,7 @@ using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace GameRes.Formats.Sakana namespace GameRes.Formats.Sakana
{ {
@ -37,6 +38,7 @@ namespace GameRes.Formats.Sakana
{ {
public int Storage; public int Storage;
public ushort Flags; public ushort Flags;
public ushort ArcIndex;
public bool IsEncrypted { get { return 0 == (Flags & 0x10); } } public bool IsEncrypted { get { return 0 == (Flags & 0x10); } }
} }
@ -57,11 +59,23 @@ namespace GameRes.Formats.Sakana
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
const uint DefaultKey = 0x2E76034B; const uint DefaultKey = 0x2E76034B;
static readonly Regex ArchiveNameRe = new Regex (@"^(.*)-([^-]+)$");
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
var sx_name = FindSxName (file.Name); var base_name = Path.GetFileNameWithoutExtension (file.Name);
if (!VFS.FileExists (sx_name) || file.Name.Equals (sx_name, StringComparison.InvariantCultureIgnoreCase)) var sx_name = base_name.Substring (0, 4) + "(00).sx";
sx_name = VFS.ChangeFileName (file.Name, sx_name);
if (!VFS.FileExists (sx_name))
{
var match = ArchiveNameRe.Match (base_name);
if (!match.Success)
return null;
sx_name = VFS.ChangeFileName (file.Name, match.Groups[1] + "(00).sx");
if (!VFS.FileExists (sx_name))
return null;
}
if (file.Name.Equals (sx_name, StringComparison.OrdinalIgnoreCase))
return null; return null;
byte[] index_data; byte[] index_data;
using (var sx = VFS.OpenView (sx_name)) using (var sx = VFS.OpenView (sx_name))
@ -86,6 +100,8 @@ namespace GameRes.Formats.Sakana
{ {
var reader = new SxIndexDeserializer (index, file.MaxOffset); var reader = new SxIndexDeserializer (index, file.MaxOffset);
var dir = reader.Deserialize(); var dir = reader.Deserialize();
if (null == dir || dir.Count == 0)
return null;
return new ArcFile (file, this, dir); return new ArcFile (file, this, dir);
} }
} }
@ -103,7 +119,11 @@ namespace GameRes.Formats.Sakana
DecryptData (input, key_lo, key_hi); DecryptData (input, key_lo, key_hi);
} }
if (sx_entry.IsPacked) if (sx_entry.IsPacked)
{
input = UnpackZstd (input); input = UnpackZstd (input);
if (sx_entry.UnpackedSize == 0)
sx_entry.UnpackedSize = (uint)input.Length;
}
return new BinMemoryStream (input, entry.Name); return new BinMemoryStream (input, entry.Name);
} }
@ -190,8 +210,7 @@ namespace GameRes.Formats.Sakana
m_dir = new List<Entry> (count); m_dir = new List<Entry> (count);
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
m_index.ReadByte(); ushort arc = Binary.BigEndian (m_index.ReadUInt16());
int storage = m_index.ReadByte();
ushort flags = Binary.BigEndian (m_index.ReadUInt16()); ushort flags = Binary.BigEndian (m_index.ReadUInt16());
uint offset = Binary.BigEndian (m_index.ReadUInt32()); uint offset = Binary.BigEndian (m_index.ReadUInt32());
uint size = Binary.BigEndian (m_index.ReadUInt32()); uint size = Binary.BigEndian (m_index.ReadUInt32());
@ -201,35 +220,32 @@ namespace GameRes.Formats.Sakana
Offset = (long)offset << 4, Offset = (long)offset << 4,
Size = size, Size = size,
IsPacked = 0 != (flags & 0x03), IsPacked = 0 != (flags & 0x03),
ArcIndex = arc,
}; };
m_dir.Add (entry); m_dir.Add (entry);
} }
count = Binary.BigEndian (m_index.ReadUInt16()); int arc_count = Binary.BigEndian (m_index.ReadUInt16());
var storages = new List<SxStorage>(count); int arc_index = -1;
for (int i = 0; i < count; ++i) for (int i = 0; i < arc_count; ++i)
{ {
m_index.ReadUInt32(); m_index.ReadUInt32();
m_index.ReadUInt32(); m_index.ReadUInt32();
m_index.ReadUInt32(); m_index.ReadUInt32();
var storage = new SxStorage { long arc_size = (long)Binary.BigEndian (m_index.ReadUInt32()) << 4; // archive body length
Size = Binary.BigEndian (m_index.ReadUInt32()) << 4, if (m_max_offset == arc_size)
Timestamp = Binary.BigEndian (m_index.ReadUInt64()), arc_index = i;
}; m_index.ReadUInt64();
storages.Add (storage); m_index.Seek (16, SeekOrigin.Current); // MD5 sum
m_index.Seek (16, SeekOrigin.Current); // MD5
} }
count = Binary.BigEndian (m_index.ReadUInt16()); count = Binary.BigEndian (m_index.ReadUInt16());
if (count > 0) if (count > 0)
m_index.Seek (count * 24, SeekOrigin.Current); m_index.Seek (count * 24, SeekOrigin.Current);
DeserializeTree(); DeserializeTree();
// Remove entries in other archives if (arc_count > 1 && arc_index != -1)
// Note using file size as archive identification can be problematic, but faster than MD5
var current_storage = storages.FindIndex (s => m_max_offset == s.Size);
if (-1 != current_storage)
{ {
m_dir = m_dir.Where (e => e.CheckPlacement (m_max_offset) && (e as SxEntry).Storage == current_storage).ToList(); return m_dir.Where (e => (e as SxEntry).ArcIndex == arc_index).ToList();
} }
return m_dir; return m_dir;
} }

View File

@ -46,6 +46,11 @@ IN THE SOFTWARE.
<scm:SortDescription PropertyName="Tag" Direction="Ascending"/> <scm:SortDescription PropertyName="Tag" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions> </CollectionViewSource.SortDescriptions>
</CollectionViewSource> </CollectionViewSource>
<CollectionViewSource x:Key="AudioFormatsSource" Source="{Binding Source={x:Static gr:FormatCatalog.Instance}, Path=AudioFormats, Mode=OneWay}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Tag" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Style x:Key="HiddenHeaderStyle" TargetType="{x:Type GridViewColumnHeader}"> <Style x:Key="HiddenHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" /> <Setter Property="Visibility" Value="Collapsed" />
</Style> </Style>
@ -126,7 +131,7 @@ IN THE SOFTWARE.
<TabItem Header="{x:Static s:guiStrings.TextAboutAudio}"> <TabItem Header="{x:Static s:guiStrings.TextAboutAudio}">
<Border BorderThickness="1" BorderBrush="Black" VerticalAlignment="Stretch" Margin="0" SnapsToDevicePixels="True"> <Border BorderThickness="1" BorderBrush="Black" VerticalAlignment="Stretch" Margin="0" SnapsToDevicePixels="True">
<ScrollViewer VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0" Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}"> <ScrollViewer VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0" Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}">
<ItemsControl Name="AudioFormats" ItemsSource="{Binding Source={x:Static gr:FormatCatalog.Instance}, Path=AudioFormats, Mode=OneWay}"> <ItemsControl Name="AudioFormats" ItemsSource="{Binding Source={StaticResource AudioFormatsSource}}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">

View File

@ -79,9 +79,9 @@ namespace GARbro.GUI
public string Name { get; set; } public string Name { get; set; }
public Entry Entry { get; set; } public Entry Entry { get; set; }
public bool IsEqual (IEnumerable<string> path, string name) public bool IsEqual (IEnumerable<string> path, Entry entry)
{ {
return Path != null && path.SequenceEqual (Path) && name.Equals (Name); return Path != null && path.SequenceEqual (Path) && Entry == entry;
} }
} }
@ -139,7 +139,7 @@ namespace GARbro.GUI
/// </summary> /// </summary>
private void PreviewEntry (Entry entry) private void PreviewEntry (Entry entry)
{ {
if (m_current_preview.IsEqual (ViewModel.Path, entry.Name)) if (m_current_preview.IsEqual (ViewModel.Path, entry))
return; return;
UpdatePreviewPane (entry); UpdatePreviewPane (entry);
} }

View File

@ -440,9 +440,22 @@ namespace GameRes
{ {
byte* s = m_mem + (offset - m_offset); byte* s = m_mem + (offset - m_offset);
uint string_length = 0; uint string_length = 0;
while (string_length < size && 0 != s[string_length]) // enc.WindowsCodePage property might throw an exception for some encodings, while
// CodePage is just a direct field access.
if (enc.CodePage == 1200 || enc.CodePage == 1201) // for UTF-16 encodings stop marker is 2-bytes long
{ {
++string_length; ushort* u = (ushort*)s;
while (string_length + 1 < size && 0 != u[string_length >> 1])
{
string_length += 2;
}
}
else
{
while (string_length < size && 0 != s[string_length])
{
++string_length;
}
} }
return new string ((sbyte*)s, 0, (int)string_length, enc); return new string ((sbyte*)s, 0, (int)string_length, enc);
} }

View File

@ -263,7 +263,9 @@ namespace GameRes
public bool FileExists (string filename) public bool FileExists (string filename)
{ {
return m_dir.ContainsKey (CombinePath (CurrentDirectory, filename)); return m_dir.ContainsKey (filename)
|| !string.IsNullOrEmpty (CurrentDirectory)
&& m_dir.ContainsKey (CombinePath (CurrentDirectory, filename));
} }
public Stream OpenStream (Entry entry) public Stream OpenStream (Entry entry)
@ -408,6 +410,8 @@ namespace GameRes
Entry entry = null; Entry entry = null;
if (m_dir.TryGetValue (filename, out entry)) if (m_dir.TryGetValue (filename, out entry))
return entry; return entry;
if (m_dir.TryGetValue (CombinePath (CurrentDirectory, filename), out entry))
return entry;
var dir_name = filename + PathDelimiter; var dir_name = filename + PathDelimiter;
if (m_dir.Keys.Any (n => n.StartsWith (dir_name))) if (m_dir.Keys.Any (n => n.StartsWith (dir_name)))
return new SubDirEntry (filename); return new SubDirEntry (filename);

View File

@ -220,6 +220,13 @@ namespace GameRes
file.Read (pixels, dst, stride); file.Read (pixels, dst, stride);
for (int x = 3; !has_alpha && x < stride; x += 4) for (int x = 3; !has_alpha && x < stride; x += 4)
has_alpha = pixels[dst+x] != 0; has_alpha = pixels[dst+x] != 0;
/* // sometimes alpha channel is inverted
for (int x = 3; x < stride; x += 4)
{
pixels[dst+x] ^= 0xFF;
has_alpha = has_alpha || pixels[dst+x] != 0;
}
*/
} }
PixelFormat format = has_alpha ? PixelFormats.Bgra32 : PixelFormats.Bgr32; PixelFormat format = has_alpha ? PixelFormats.Bgra32 : PixelFormats.Bgr32;
return ImageData.Create (info, format, null, pixels, stride); return ImageData.Create (info, format, null, pixels, stride);

View File

@ -122,7 +122,7 @@ namespace GameRes
protected ImageData m_image; protected ImageData m_image;
public Stream Source { get { m_input.Position = 0; return m_input.AsStream; } } public Stream Source { get { m_input.Position = 0; return m_input.AsStream; } }
public ImageFormat SourceFormat { get { return null; } } public ImageFormat SourceFormat { get; protected set; }
public ImageMetaData Info { get; protected set; } public ImageMetaData Info { get; protected set; }
public ImageData Image { get { return m_image ?? (m_image = GetImageData()); } } public ImageData Image { get { return m_image ?? (m_image = GetImageData()); } }

View File

@ -26,7 +26,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
// [000225][Light Plan] My Fairink Yousei Byakuya Monogatari // [000225][Light Plan] My Fair Link Yousei Byakuya Monogatari
namespace GameRes.Formats.Gsx namespace GameRes.Formats.Gsx
{ {

70
Legacy/Gsx/ArcK5.cs Normal file
View File

@ -0,0 +1,70 @@
//! \file ArcK5.cs
//! \date 2023 Aug 27
//! \brief GSX engine resource archive.
//
// Copyright (C) 2023 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.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Text;
namespace GameRes.Formats.Gsx
{
[Export(typeof(ArchiveFormat))]
public class K5Opener : ArchiveFormat
{
public override string Tag => "K5";
public override string Description => "GSX engine resource archive";
public override uint Signature => 0x01354B; // 'K5'
public override bool IsHierarchic => true;
public override bool CanWrite => false;
public K5Opener ()
{
ContainedFormats = new[] { "K4", "OGG" };
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
uint index_offset = file.View.ReadUInt32 (8);
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var dir_name = file.View.ReadString (index_offset, 0x80, Encoding.Unicode);
var name = file.View.ReadString (index_offset+0x80, 0x40, Encoding.Unicode);
name = Path.Combine (dir_name, name);
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_offset+0xC8);
entry.Size = file.View.ReadUInt32 (index_offset+0xCC);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_offset += 0x100;
}
return new ArcFile (file, this, dir);
}
}
}

View File

@ -1,8 +1,8 @@
//! \file ImageK4.cs //! \file ImageK4.cs
//! \date 2019 Feb 07 //! \date 2023 Aug 27
//! \brief Toyo GSX image format. //! \brief GSX engine image format.
// //
// Copyright (C) 2019 by morkt // Copyright (C) 2023 by morkt
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to // of this software and associated documentation files (the "Software"), to
@ -23,6 +23,8 @@
// IN THE SOFTWARE. // IN THE SOFTWARE.
// //
using GameRes.Utility;
using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows.Media; using System.Windows.Media;
@ -33,16 +35,16 @@ namespace GameRes.Formats.Gsx
{ {
internal class K4MetaData : ImageMetaData internal class K4MetaData : ImageMetaData
{ {
public bool HasAlpha; public byte AlphaMode;
public int FrameCount; public int FrameCount;
} }
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
public class K4Format : ImageFormat public class K4Format : ImageFormat
{ {
public override string Tag { get { return "K4"; } } public override string Tag => "K4";
public override string Description { get { return "Toyo GSX image format"; } } public override string Description => "GSX engine image format";
public override uint Signature { get { return 0x0201344B; } } // 'K4' public override uint Signature => 0x0201344B; // 'K4'
public override ImageMetaData ReadMetaData (IBinaryStream file) public override ImageMetaData ReadMetaData (IBinaryStream file)
{ {
@ -51,20 +53,22 @@ namespace GameRes.Formats.Gsx
return null; return null;
if (header[2] != 1 || header[3] != 2) if (header[2] != 1 || header[3] != 2)
return null; return null;
int frame_count = header.ToInt16 (0xC);
if (frame_count <= 0)
return null;
return new K4MetaData { return new K4MetaData {
Width = header.ToUInt16 (4), Width = header.ToUInt16 (4),
Height = header.ToUInt16 (6), Height = header.ToUInt16 (6),
BPP = header[0xF], BPP = header[0xF],
HasAlpha = header[0xB] != 0, AlphaMode = header[0xB],
FrameCount = header.ToUInt16 (0xC), FrameCount = frame_count,
}; };
} }
public override ImageData Read (IBinaryStream file, ImageMetaData info) public override ImageData Read (IBinaryStream file, ImageMetaData info)
{ {
var meta = (K4MetaData)info; var reader = new K4Reader (file, (K4MetaData)info);
return reader.Unpack();
return ImageData.Create (info, format, palette, pixels);
} }
public override void Write (Stream file, ImageData image) public override void Write (Stream file, ImageData image)
@ -72,4 +76,190 @@ namespace GameRes.Formats.Gsx
throw new System.NotImplementedException ("K4Format.Write not implemented"); throw new System.NotImplementedException ("K4Format.Write not implemented");
} }
} }
internal sealed class K4Reader
{
IBinaryStream m_input;
K4MetaData m_info;
public K4Reader (IBinaryStream input, K4MetaData info)
{
m_input = input;
m_info = info;
}
int m_stride;
int m_pixel_size;
public ImageData Unpack ()
{
uint base_offset = 0x30;
m_input.Position = base_offset;
m_info.Width = m_input.ReadUInt16();
m_info.Height = m_input.ReadUInt16();
m_input.Seek (8, SeekOrigin.Current);
int bpp = m_input.ReadUInt16();
int flags = m_input.ReadUInt16();
m_input.Seek (4, SeekOrigin.Current);
uint alpha_pos = m_input.ReadUInt32();
m_input.Seek (12, SeekOrigin.Current);
int ctl_length = m_input.ReadInt32();
m_pixel_size = bpp / 8;
m_stride = (m_info.iWidth * m_pixel_size + 3) & ~3;
var pixels = new byte[m_info.iHeight * m_stride];
int dst = 0;
bool do_delta = (flags & 1) != 0;
var control_bytes = m_input.ReadBytes (ctl_length - 0x10);
using (var mem = new MemoryStream (control_bytes))
using (var ctl = new MsbBitStream (mem))
using (var data = new MsbBitStream (m_input.AsStream, true))
{
while (dst < pixels.Length)
{
int b = ctl.GetNextBit();
if (-1 == b)
break;
if (b != 0)
{
if (!do_delta)
{
pixels[dst++] = (byte)data.GetBits (8);
}
else if (dst >= m_pixel_size)
{
pixels[dst] = (byte)(pixels[dst - m_pixel_size] + data.GetBits (9) + 1);
++dst;
}
else
{
pixels[dst++] = (byte)data.GetBits (9);
}
}
else
{
int pos, count;
if (ctl.GetNextBit() != 0)
{
pos = data.GetBits (14);
count = data.GetBits (4) + 3;
}
else
{
pos = data.GetBits (9);
count = data.GetBits (3) + 2;
}
int src = dst - pos - 1;
count = Math.Min (count, pixels.Length - dst);
if (!do_delta || dst < m_pixel_size)
{
Binary.CopyOverlapped (pixels, src, dst, count);
dst += count;
}
else
{
while (count --> 0)
{
pixels[dst++] = (byte)(pixels[src] + pixels[src + pos - m_pixel_size + 1] - pixels[src - m_pixel_size]);
++src;
}
}
}
}
}
if (0 == alpha_pos)
{
if (24 == bpp)
return ImageData.CreateFlipped (m_info, PixelFormats.Bgr24, null, pixels, m_stride);
else
return ImageData.CreateFlipped (m_info, PixelFormats.Bgr32, null, pixels, m_stride);
}
if (0xFF == m_info.AlphaMode)
pixels = UnpackAlphaFF (alpha_pos + base_offset, pixels);
else if (0xFE == m_info.AlphaMode)
pixels = UnpackAlphaFE (alpha_pos + base_offset, pixels);
else
throw new NotSupportedException (string.Format ("Not supported alpha channel mode 0x{0:X2}", m_info.AlphaMode));
m_stride = m_info.iWidth * 4;
return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels, m_stride);
}
byte[] UnpackAlphaFF (uint alpha_pos, byte[] pixels)
{
m_input.Position = alpha_pos;
var offsets = new int[m_info.iHeight];
for (int i = 0; i < offsets.Length; ++i)
offsets[i] = m_input.ReadInt32();
var output = new byte[m_info.iWidth * m_info.iHeight * 4];
int dst = 0;
for (int y = 0; y < m_info.iHeight; y++)
{
m_input.Position = alpha_pos + offsets[y];
int src = (m_info.iHeight - y - 1) * m_stride;
int dst_a = dst + 3;
for (int x = 0; x < m_info.iWidth; ++x)
{
output[dst ] = pixels[src ];
output[dst+1] = pixels[src+1];
output[dst+2] = pixels[src+2];
dst += 4;
src += m_pixel_size;
}
for (int x = 0; x < m_info.iWidth; )
{
byte alpha = m_input.ReadUInt8();
int count = m_input.ReadUInt8();
count = Math.Min (count, m_info.iWidth - x);
x += count;
if (alpha > 0)
{
alpha = (byte)((alpha * 0xFF) >> 7);
while (count --> 0)
{
output[dst_a] = alpha;
dst_a += 4;
}
}
else
{
dst_a += 4 * count;
}
}
}
return output;
}
byte[] UnpackAlphaFE (uint alpha_pos, byte[] pixels)
{
m_input.Position = alpha_pos;
var output = new byte[m_info.iWidth * m_info.iHeight * 4];
int dst = 0;
for (int y = 0; y < m_info.iHeight; y++)
{
int src = (m_info.iHeight - y - 1) * m_stride;
int dst_a = dst + 3;
for (int x = 0; x < m_info.iWidth; ++x)
{
output[dst ] = pixels[src ];
output[dst+1] = pixels[src+1];
output[dst+2] = pixels[src+2];
dst += 4;
src += m_pixel_size;
}
for (int x = 0; x < m_info.iWidth; x += 8)
{
byte alpha = m_input.ReadUInt8();
int count = Math.Min (8, m_info.iWidth - x);
for (int i = 0; i < count; ++i)
{
output[dst_a] = (byte)-(alpha & 1);
dst_a += 4;
alpha >>= 1;
}
}
}
return output;
}
}
} }

421
Legacy/HyperWorks/ImageG.cs Normal file
View File

@ -0,0 +1,421 @@
//! \file ImageG.cs
//! \date 2023 Aug 28
//! \brief HyperWorks image format.
//
// Copyright (C) 2023 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 GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [951207][Love Gun] ACE OF SPADES
// I24 predecessor
namespace GameRes.Formats.HyperWorks
{
[Export(typeof(ImageFormat))]
public class GFormat : ImageFormat
{
public override string Tag => "G";
public override string Description => "HyperWorks indexed image format";
public override uint Signature => 0x1A477D00;
public GFormat ()
{
Signatures = new[] { 0x1A477D00u, 0u };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (12);
if (header.ToUInt16 (2) != 0x1A47)
return null;
// not sure if 0x7D00 is a required signature, so rely on filename
if (header.ToUInt16 (0) != 0x7D00 && !file.Name.HasExtension (".G"))
return null;
return new ImageMetaData {
Width = header.ToUInt16 (8),
Height = header.ToUInt16 (10),
OffsetX = header.ToInt16 (4),
OffsetY = header.ToInt16 (6),
BPP = 8,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new GReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GFormat.Write not implemented");
}
}
internal sealed class GReader
{
IBinaryStream m_input;
ImageMetaData m_info;
int m_stride;
byte[] m_palette_data;
byte[] m_output;
ushort[] m_buffer;
int[] m_line_ptr;
public GReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
m_stride = (m_info.iWidth * m_info.BPP / 8 + 1) & -2;
int s = Math.Max (0x142, m_stride / 2 + 2); // line buffer size
m_buffer = new ushort[s * 3 + 1];
m_line_ptr = new int[3] { 1, 1 + s, 1 + s*2 };
}
public ImageData Unpack ()
{
m_input.Position = 0x0C;
m_palette_data = m_input.ReadBytes (0x30);
m_input.Position = 0x40;
int width = ((m_info.iWidth + 7) & -8);
int rows = ((m_info.iHeight + 1) & -2);
m_output = new byte[m_stride * rows];
InitColorTable();
int blockW = width >> 1;
int blockH = rows >> 1;
SetupBitReader();
for (int y = 0; y < blockH; ++y)
{
var dst = m_line_ptr[2];
m_line_ptr[2] = m_line_ptr[1];
m_line_ptr[1] = m_line_ptr[0];
m_line_ptr[0] = dst;
int x = 0;
while (x < blockW)
{
if (GetNextBit() != 0)
{
m_buffer[dst++] = GetColorFromTable (x);
++x;
}
else
{
int count = ExtractBits (BitTable1);
if (count >= 0x40)
count += ExtractBits (BitTable1);
int idx = ExtractBits (BitTable2) * 2;
int src = m_line_ptr[OffTable[idx + 1]];
src += OffTable[idx] + x;
x += count;
while (count --> 0)
{
m_buffer[dst++] = m_buffer[src++];
}
}
}
UnpackRow (y, blockW, m_line_ptr[0]);
}
var palette = UnpackPalette();
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, m_output, m_stride);
}
void UnpackRow (int y, int width, int buf_pos)
{
int row1 = m_stride * y * 2;
int row2 = row1 + m_stride;
for (int i = 0; i < width; ++i)
{
ushort v = m_buffer[buf_pos++];
ushort v0 = (ushort)((v & 0xF00 | ((v & 0xF000) >> 12)) + 0xA0A);
LittleEndian.Pack (v0, m_output, row2);
row2 += 2;
ushort v1 = (ushort)((((v & 0xF) << 8) | ((v & 0xF0) >> 4)) + 0xA0A);
LittleEndian.Pack (v1, m_output, row1);
row1 += 2;
}
}
byte[] g_palIndexes = { 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 0xC, 0xD, 0xA, 0xB, 0xE, 0xF };
BitmapPalette UnpackPalette ()
{
var colors = new Color[42];
for (int i = 0; i < 16; ++i)
{
int R = m_palette_data[3 * g_palIndexes[i] ]; R |= R << 4;
int G = m_palette_data[3 * g_palIndexes[i] + 1]; G |= G << 4;
int B = m_palette_data[3 * g_palIndexes[i] + 2]; B |= B << 4;
colors[i+10] = Color.FromRgb ((byte)R, (byte)G, (byte)B);
int b = R & 1;
int c = (sbyte)R >> 1;
if (c < 0)
c += b;
colors[i+26].R = (byte)c;
b = G & 1;
c = (sbyte)G >> 1;
if (c < 0)
c += b;
colors[i+26].G = (byte)c;
b = B & 1;
c = (sbyte)B >> 1;
if (c < 0)
c += b;
colors[i+26].B = (byte)c;
}
return new BitmapPalette (colors);
}
byte[] g_colorTable = new byte[256];
void InitColorTable ()
{
int dst = 0;
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 16; ++j)
g_colorTable[dst++] = (byte)((j + i + 1) & 0xF);
}
ushort GetColorFromTable (int x)
{
ushort b0 = m_buffer[m_line_ptr[1] + x];
int n0 = b0 & 0xF;
int n1 = (b0 >> 4) & 0xF;
int n2 = (b0 >> 8) & 0xF;
int n3 = (b0 >> 12) & 0xF;
ushort b1 = m_buffer[m_line_ptr[1] + x - 1];
int m0 = b1 & 0xF;
int m1 = (b1 >> 4) & 0xF;
int m2 = (b1 >> 8) & 0xF;
int m3 = (b1 >> 12) & 0xF;
ushort b2 = m_buffer[m_line_ptr[0] + x - 1];
int p0 = b2 & 0xF;
int p1 = (b2 >> 4) & 0xF;
int p2 = (b2 >> 8) & 0xF;
int p3 = (b2 >> 12) & 0xF;
int r1 = n1;
if (n1 != n3 && (n1 != m1 || n1 != p1))
{
if (p0 == p1)
r1 = p0;
else
r1 = m2;
}
if (GetNextBit() != 0)
r1 = AdjustColorTable (r1);
int r0 = n0;
if (n0 != n2 && (n0 != m0 || n0 != p0))
{
if (r1 == p1)
r0 = p1;
else
r0 = n3;
}
if (GetNextBit() != 0)
r0 = AdjustColorTable (r0);
int r3 = n3;
if (r1 != n3 && (n3 != m3 || n3 != p3))
{
if (p2 == p3)
r3 = p2;
else
r3 = p0;
}
if (GetNextBit() != 0)
r3 = AdjustColorTable (r3);
int r2 = n2;
if (n2 != r0 && (n2 != m2 || n2 != p2))
{
if (p3 == r3)
r2 = p3;
else
r2 = r1;
}
if (GetNextBit() != 0)
r2 = AdjustColorTable (r2);
return (ushort)((r3 << 12) | (r2 << 8) | (r1 << 4) | r0);
}
byte AdjustColorTable (int idx)
{
int shift_count = ExtractBits (BitTable3);
int i = 16 * idx + shift_count;
byte c = g_colorTable[i];
if (shift_count != 0)
{
while (shift_count --> 0)
{
g_colorTable[i] = g_colorTable[i-1];
--i;
}
g_colorTable[i] = c;
}
return c;
}
int ExtractBits (byte[] table)
{
int idx = ((bits >> 8) & 0xFF) << 1;
int n = table[idx];
if (n != 0)
{
if (n >= bitCount)
{
n -= bitCount;
bits <<= bitCount;
int b = m_input.ReadByte();
if (b != -1) // XXX ignore EOF
bits |= b;
bitCount = 8;
}
bits <<= n;
bitCount -= n;
return table[idx+1];
}
else
{
bits <<= bitCount;
int b = m_input.ReadByte();
if (b != -1) // XXX ignore EOF
bits |= b;
bits <<= 8 - bitCount;
int t = table[idx+1];
do
{
int i = GetNextBit();
t = OffTable[2 * t + 54 + i];
}
while (OffTable[2 * t + 54] != 0);
return OffTable[2 * t + 55];
}
}
int bits;
int bitCount;
private void SetupBitReader ()
{
bits = m_input.ReadUInt8() << 8;
bits |= m_input.ReadUInt8();
bitCount = 8;
}
private int GetNextBit ()
{
bits <<= 1;
if (0 == --bitCount)
{
int b = m_input.ReadByte();
if (b != -1) // XXX ignore EOF
bits |= b;
bitCount = 8;
}
return (bits >> 16) & 1;
}
static readonly byte[] BitTable1 = new byte[] {
3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 8,
0xD, 0, 0, 0, 1, 8, 0xE, 6, 7, 6, 7, 6, 7, 6, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
7, 0xA, 7, 0xA, 0, 2, 0, 7, 7, 0xB, 7, 0xB, 8, 0xF, 0, 3, 6, 8, 6, 8, 6, 8, 6, 8, 8, 0x10, 0, 4,
0, 5, 8, 0x11, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 7, 0xC, 7, 0xC, 0, 8, 0, 6, 6, 9, 6,
9, 6, 9, 6, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};
static readonly byte[] BitTable2 = new byte[] {
5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 7, 0x17, 7, 0x17, 7, 0x16, 7, 0x16, 6, 0x0E, 6,
0x0E, 6, 0x0E, 6, 0x0E, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4,
3, 4, 3, 4, 3, 4, 3, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0C, 6, 0x0C, 6, 0x0C, 6, 0x0C, 5, 6,
5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 7, 0x14, 7,
0x14, 7, 0x18, 7, 0x18, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x11, 6, 0x11, 6, 0x11, 6, 0x11, 6,
0x0F, 6, 0x0F, 6, 0x0F, 6, 0x0F, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5,
0x0A, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1,
6, 0x13, 6, 0x13, 6, 0x13, 6, 0x13, 6, 0x12, 6, 0x12, 6, 0x12, 6, 0x12, 5, 0x0B, 5, 0x0B, 5, 0x0B,
5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 7, 0x19, 7, 0x19, 7, 0x1A, 7, 0x1A, 6, 0x15, 6, 0x15,
6, 0x15, 6, 0x15, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2,
3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
};
static readonly byte[] BitTable3 = new byte[] {
4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5,
4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 7, 8, 8, 0x0A, 8, 0x0B,
4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 6, 7, 6, 7, 6, 7, 7, 9, 7, 9, 0, 0x5F, 8, 0x0C,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
};
static readonly short[] OffTable = new short[] {
0, 1, 1, 1, -1, 0, -1, 1, -4, 0, -2, 0, 2, 1, -2, 1, 4, 1, 0, 2, 1, 2, -1, 2, -8, 0, -4, 1, 8, 1,
-2, 2, 2, 2, -4, 2, 4, 2, 8, 2, -8, 1, -0x10, 0, -8, 2, 0x10, 1, 0x10, 2, -0x10, 1, -0x10, 2,
0x2C, 9, 0x2D, 0x0A, 0x2E, 0x0B, 0x2F, 0x0C, 0x30, 0x0D, 0x31, 0x32, 0x0E, 0x33, 0x0F, 0x10, 0x11,
0x12, 0x13, 0x14, 0x34, 0x15, 0x37, 0x35, 0x16, 0x38, 0x17, 0x39, 0x3D, 0x18, 0x19, 0x36, 0x1A,
0x1B, 0x3A, 0x3C, 0x1C, 0x3B, 0x1D, 0x3E, 0x3F, 0x1E, 0x40, 0x1F, 0x45, 0x46, 0x43, 0x20, 0x21,
0x48, 0x22, 0x41, 0x23, 0x42, 0x24, 0x25, 0x44, 0x47, 0x4F, 0x4A, 0x49, 0x53, 0x4B, 0x4D, 0x57,
0x52, 0x59, 0x58, 0x4E, 0x50, 0x56, 0x26, 0x54, 0x4C, 0x51, 0x55, 0x27, 0x28, 0x5A, 0x2B, 0x29,
0x5B, 0x2A, 0x5C, 0x5D, 0x5E, 0, 0, 0, 0x12, 0, 0x13, 0, 0x14, 0, 0x15, 0, 0x16, 0, 0x17, 0, 0x18,
0, 0x19, 0, 0x1A, 0, 0x1B, 0, 0x1C, 0, 0x1D, 0, 0x1E, 0, 0x1F, 0, 0x20, 0, 0x21, 0, 0x22, 0, 0x23,
0, 0x24, 0, 0x25, 0, 0x26, 0, 0x27, 0, 0x28, 0, 0x29, 0, 0x2A, 0, 0x2B, 0, 0x2C, 0, 0x2D, 0, 0x2E,
0, 0x2F, 0, 0x30, 0, 0x31, 0, 0x32, 0, 0x33, 0, 0x34, 0, 0x35, 0, 0x36, 0, 0x37, 0, 0x38, 0, 0x39,
0, 0x3A, 0, 0x3B, 0, 0x3C, 0, 0x3D, 0, 0x3E, 0, 0x3F, 0, 0x40, 0, 0x80, 0, 0x0C0, 0, 0x100, 0,
0x140, 0x60, 0x61, 0, 0x0D, 0, 0x0E,
};
}
}

View File

@ -23,22 +23,32 @@
// IN THE SOFTWARE. // IN THE SOFTWARE.
// //
using GameRes.Utility;
using System; using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows.Media; using System.Windows.Media;
// [970905][Ucom] Winter Kiss
// [980626][Love Gun] ACE OF SPADES 2 // [980626][Love Gun] ACE OF SPADES 2
namespace GameRes.Formats.HyperWorks namespace GameRes.Formats.HyperWorks
{ {
internal class I24MetaData : ImageMetaData
{
public byte Version;
}
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
public class I24Format : ImageFormat public class I24Format : ImageFormat
{ {
public override string Tag { get { return "I24"; } } public override string Tag => "I24";
public override string Description { get { return "HyperWorks image format"; } } public override string Description => "HyperWorks RGB image format";
public override uint Signature { get { return 0x41343249; } } // 'I24A' public override uint Signature => 0x41343249; // 'I24A'
public I24Format ()
{
Signatures = new[] { 0x41343249u, 0x20343249u };
}
public override ImageMetaData ReadMetaData (IBinaryStream file) public override ImageMetaData ReadMetaData (IBinaryStream file)
{ {
@ -46,16 +56,17 @@ namespace GameRes.Formats.HyperWorks
int bpp = header.ToInt16 (0x10); int bpp = header.ToInt16 (0x10);
if (bpp != 24) if (bpp != 24)
return null; return null;
return new ImageMetaData { return new I24MetaData {
Width = header.ToUInt16 (0xC), Width = header.ToUInt16 (0xC),
Height = header.ToUInt16 (0xE), Height = header.ToUInt16 (0xE),
BPP = bpp, BPP = bpp,
Version = header[3],
}; };
} }
public override ImageData Read (IBinaryStream file, ImageMetaData info) public override ImageData Read (IBinaryStream file, ImageMetaData info)
{ {
var reader = new I24Decoder (file, info); var reader = new I24Decoder (file, (I24MetaData)info);
return reader.Unpack(); return reader.Unpack();
} }
@ -68,9 +79,9 @@ namespace GameRes.Formats.HyperWorks
internal class I24Decoder internal class I24Decoder
{ {
IBinaryStream m_input; IBinaryStream m_input;
ImageMetaData m_info; I24MetaData m_info;
public I24Decoder (IBinaryStream input, ImageMetaData info) public I24Decoder (IBinaryStream input, I24MetaData info)
{ {
m_input = input; m_input = input;
m_info = info; m_info = info;
@ -170,14 +181,24 @@ namespace GameRes.Formats.HyperWorks
short s2 = shift_table[shift_idx + 1]; short s2 = shift_table[shift_idx + 1];
if (shift_token != 0) if (shift_token != 0)
{ {
while (shift_token --> 0) if (m_info.Version == 'A')
{ {
shift_table[shift_idx] = shift_table[shift_idx - 2]; while (shift_idx > 0)
shift_table[shift_idx+1] = shift_table[shift_idx - 1]; {
shift_idx -= 2; shift_table[shift_idx] = shift_table[shift_idx - 2];
shift_table[shift_idx+1] = shift_table[shift_idx - 1];
shift_idx -= 2;
}
shift_table[0] = s1;
shift_table[1] = s2;
}
else
{
shift_table[shift_idx ] = shift_table[shift_idx - 2];
shift_table[shift_idx + 1] = shift_table[shift_idx - 1];
shift_table[shift_idx - 2] = s1;
shift_table[shift_idx - 1] = s2;
} }
shift_table[0] = s1;
shift_table[1] = s2;
} }
int src = 4 * (x + s1); int src = 4 * (x + s1);
if (color_token >= 216) if (color_token >= 216)

View File

@ -30,19 +30,20 @@ using System.IO;
using System.Text; using System.Text;
using GameRes.Compression; using GameRes.Compression;
// [971205][Azlocks] Isle Mystique
// [991001][Inspire] days innocent // [991001][Inspire] days innocent
// [000707][inspire] ambience // [000707][inspire] ambience
namespace GameRes.Formats.Inspire namespace GameRes.Formats.Inspire
{ {
internal class IdaEntry : Entry internal class IdaEntry : PackedEntry
{ {
public uint Flags; public uint Flags;
public uint Key; public uint Key;
} }
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat public class IdaOpener : ArchiveFormat
{ {
public override string Tag { get { return "IDA"; } } public override string Tag { get { return "IDA"; } }
public override string Description { get { return "Inspire resource archive"; } } public override string Description { get { return "Inspire resource archive"; } }
@ -50,6 +51,11 @@ namespace GameRes.Formats.Inspire
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public IdaOpener ()
{
Extensions = new[] { "ida", "mha" };
}
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
int version = file.View.ReadInt32 (4); int version = file.View.ReadInt32 (4);
@ -58,8 +64,9 @@ namespace GameRes.Formats.Inspire
using (var index = file.CreateStream()) using (var index = file.CreateStream())
{ {
var dir = new List<Entry>(); var dir = new List<Entry>();
bool has_packed = false;
long index_pos = 8; long index_pos = 8;
for (;;) do
{ {
index.Position = index_pos; index.Position = index_pos;
uint entry_length = index.ReadUInt32(); uint entry_length = index.ReadUInt32();
@ -76,15 +83,27 @@ namespace GameRes.Formats.Inspire
var entry = FormatCatalog.Instance.Create<IdaEntry> (name); var entry = FormatCatalog.Instance.Create<IdaEntry> (name);
entry.Offset = offset; entry.Offset = offset;
entry.Size = size; entry.Size = entry.UnpackedSize = size;
if (!entry.CheckPlacement (file.MaxOffset)) if (offset > file.MaxOffset || offset < index_pos)
return null; return null;
entry.IsPacked = (flags & 0x14) != 0;
entry.Flags = flags; entry.Flags = flags;
entry.Key = key; entry.Key = key;
has_packed = has_packed || entry.IsPacked;
dir.Add (entry); dir.Add (entry);
} }
while (index_pos < dir[0].Offset);
if (0 == dir.Count) if (0 == dir.Count)
return null; return null;
if (has_packed) // set proper sizes
{
long last_offset = file.MaxOffset;
for (int i = dir.Count - 1; i >= 0; --i)
{
dir[i].Size = (uint)(last_offset - dir[i].Offset);
last_offset = dir[i].Offset;
}
}
return new ArcFile (file, this, dir); return new ArcFile (file, this, dir);
} }
} }

View File

@ -90,8 +90,11 @@
<Compile Include="Asura\ImageMTG.cs" /> <Compile Include="Asura\ImageMTG.cs" />
<Compile Include="BlackButterfly\ArcDAT.cs" /> <Compile Include="BlackButterfly\ArcDAT.cs" />
<Compile Include="CottonClub\ImageLMG.cs" /> <Compile Include="CottonClub\ImageLMG.cs" />
<Compile Include="Gsx\ArcK5.cs" />
<Compile Include="Gsx\ImageK4.cs" />
<Compile Include="Herb\ArcPAK.cs" /> <Compile Include="Herb\ArcPAK.cs" />
<Compile Include="Herb\ImageGRP.cs" /> <Compile Include="Herb\ImageGRP.cs" />
<Compile Include="HyperWorks\ImageG.cs" />
<Compile Include="James\ImageJMG.cs" /> <Compile Include="James\ImageJMG.cs" />
<Compile Include="BRoom\ArcCPC.cs" /> <Compile Include="BRoom\ArcCPC.cs" />
<Compile Include="BRoom\ArcPK.cs" /> <Compile Include="BRoom\ArcPK.cs" />
@ -125,6 +128,8 @@
<Compile Include="Lazycrew\ImageDAT.cs" /> <Compile Include="Lazycrew\ImageDAT.cs" />
<Compile Include="Liddell\ArcFLK.cs" /> <Compile Include="Liddell\ArcFLK.cs" />
<Compile Include="Liddell\ImageBPA.cs" /> <Compile Include="Liddell\ImageBPA.cs" />
<Compile Include="Logg\ArcARF.cs" />
<Compile Include="Logg\ImageFRM.cs" />
<Compile Include="Melonpan\ArcTTD.cs" /> <Compile Include="Melonpan\ArcTTD.cs" />
<Compile Include="Mermaid\AudioPWV.cs" /> <Compile Include="Mermaid\AudioPWV.cs" />
<Compile Include="Mermaid\ImageGP1.cs" /> <Compile Include="Mermaid\ImageGP1.cs" />
@ -132,6 +137,7 @@
<Compile Include="Mink\ImageFD.cs" /> <Compile Include="Mink\ImageFD.cs" />
<Compile Include="Mmfass\ArcSDA.cs" /> <Compile Include="Mmfass\ArcSDA.cs" />
<Compile Include="Nyoken\ArcZLK.cs" /> <Compile Include="Nyoken\ArcZLK.cs" />
<Compile Include="Omi\ArcDAT.cs" />
<Compile Include="Paprika\ArcPKDAT.cs" /> <Compile Include="Paprika\ArcPKDAT.cs" />
<Compile Include="Paprika\ImageNP.cs" /> <Compile Include="Paprika\ImageNP.cs" />
<Compile Include="PenguinWorks\ArcPAC.cs" /> <Compile Include="PenguinWorks\ArcPAC.cs" />
@ -145,8 +151,10 @@
<Compile Include="PrimeSoft\ImageTHP.cs" /> <Compile Include="PrimeSoft\ImageTHP.cs" />
<Compile Include="ProjectMyu\ImageGAM.cs" /> <Compile Include="ProjectMyu\ImageGAM.cs" />
<Compile Include="Ransel\ArcBCD.cs" /> <Compile Include="Ransel\ArcBCD.cs" />
<Compile Include="Rare\ArcX.cs" />
<Compile Include="Regrips\AudioWRG.cs" /> <Compile Include="Regrips\AudioWRG.cs" />
<Compile Include="Regrips\ImagePRG.cs" /> <Compile Include="Regrips\ImagePRG.cs" />
<Compile Include="Rhss\ArcCRG.cs" />
<Compile Include="Rina\ImageRAD.cs" /> <Compile Include="Rina\ImageRAD.cs" />
<Compile Include="RSystem\ArcRAD.cs" /> <Compile Include="RSystem\ArcRAD.cs" />
<Compile Include="RSystem\ImageRSG.cs" /> <Compile Include="RSystem\ImageRSG.cs" />

141
Legacy/Logg/ArcARF.cs Normal file
View File

@ -0,0 +1,141 @@
//! \file ArcARF.cs
//! \date 2023 Sep 03
//! \brief Logg resource archive.
//
// Copyright (C) 2023 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 GameRes.Utility;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
// [980417][Logg] Tenshi Kourin
// [980828][Logg] Kazeiro no Romance
namespace GameRes.Formats.Logg
{
[Export(typeof(ArchiveFormat))]
public class ArfOpener : ArchiveFormat
{
public override string Tag => "ARF";
public override string Description => "Logg archive file";
public override uint Signature => 0;
public override bool IsHierarchic => true;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0);
if (!IsSaneCount (count))
return null;
uint index = 4;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
uint offset = file.View.ReadUInt32 (index);
if (offset <= index || offset > file.MaxOffset)
return null;
uint size = file.View.ReadUInt32 (index+4);
byte name_len = file.View.ReadByte (index+8);
var name = file.View.ReadString (index+9, name_len);
var entry = Create<PackedEntry> (name);
entry.Offset = offset;
entry.UnpackedSize = size;
dir.Add (entry);
index += name_len + 9u;
if (index > dir[0].Offset)
return null;
}
long last_offset = file.MaxOffset;
for (int i = count-1; i >= 0; --i)
{
var entry = dir[i] as PackedEntry;
entry.Size = (uint)(last_offset - entry.Offset);
last_offset = entry.Offset;
if (string.IsNullOrEmpty (entry.Name))
dir.RemoveAt (i);
else
entry.IsPacked = entry.Size != entry.UnpackedSize;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
if (!pent.IsPacked)
return base.OpenEntry (arc, entry);
var input = arc.File.CreateStream (pent.Offset, pent.Size);
var output = new byte[pent.UnpackedSize];
Decompress (input, output);
return new BinMemoryStream (output, pent.Name);
}
void Decompress (IBinaryStream input, byte[] output)
{
using (var bits = new LsbBitStream (input.AsStream, true))
{
int dst = 0;
while (dst < output.Length)
{
if (bits.GetNextBit() == 0)
{
output[dst++] = (byte)bits.GetBits (8);
}
else
{
int count;
if (bits.GetNextBit() == 0)
count = 2;
else if (bits.GetNextBit() == 0)
count = 3;
else if (bits.GetNextBit() == 0)
count = 4;
else if (bits.GetNextBit() == 0)
count = 5;
else
{
switch (bits.GetBits (2))
{
case 0: count = 6; break;
case 1: count = bits.GetBits (2) + 7; break;
case 2: count = bits.GetBits (4) + 11; break;
case 3: count = bits.GetBits (10) + 26; break;
default: throw new EndOfStreamException();
}
}
int offset;
if (bits.GetNextBit() == 0)
offset = bits.GetBits (8);
else if (bits.GetNextBit() == 0)
offset = bits.GetBits (10) + 0x100;
else
offset = bits.GetBits (12) + 0x500;
Binary.CopyOverlapped (output, dst - offset - 1, dst, count);
dst += count;
}
}
}
}
}
}

View File

@ -36,11 +36,11 @@ namespace GameRes.Formats.Logg
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public class MbmOpener : ArchiveFormat public class MbmOpener : ArchiveFormat
{ {
public override string Tag { get { return "MBM"; } } public override string Tag => "MBM";
public override string Description { get { return "Logg Adv engine resource archive"; } } public override string Description => "Logg Adv engine resource archive";
public override uint Signature { get { return 0; } } public override uint Signature => 0;
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic => false;
public override bool CanWrite { get { return false; } } public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
@ -63,19 +63,27 @@ namespace GameRes.Formats.Logg
IDictionary<uint, string> GetArchiveIndex (ArcView file) IDictionary<uint, string> GetArchiveIndex (ArcView file)
{ {
uint last_offset = FileListMap.Value.Keys.Last(); string list_name;
if (!ArcSizeToFileListMap.TryGetValue ((uint)file.MaxOffset, out list_name))
return null;
var file_map = ReadFileList (list_name);
uint last_offset = file_map.Keys.Last();
if (last_offset != file.MaxOffset) if (last_offset != file.MaxOffset)
return null; return null;
return FileListMap.Value; return file_map;
} }
Lazy<IDictionary<uint, string>> FileListMap = new Lazy<IDictionary<uint, string>> (ReadFileList); static readonly Dictionary<uint, string> ArcSizeToFileListMap = new Dictionary<uint, string> {
{ 0x0AB0F5F4, "logg_pl.lst" },
{ 0x0BFFD3DA, "logg_ak.lst" },
{ 0x09809196, "logg_th.lst" },
};
static IDictionary<uint, string> ReadFileList () static IDictionary<uint, string> ReadFileList (string list_name)
{ {
var file_map = new SortedDictionary<uint,string>(); var file_map = new SortedDictionary<uint,string>();
var comma = new char[] {','}; var comma = new char[] {','};
FormatCatalog.Instance.ReadFileList ("logg_pl.lst", line => { FormatCatalog.Instance.ReadFileList (list_name, line => {
var parts = line.Split (comma, 2); var parts = line.Split (comma, 2);
uint offset = uint.Parse (parts[0], NumberStyles.HexNumber); uint offset = uint.Parse (parts[0], NumberStyles.HexNumber);
if (2 == parts.Length) if (2 == parts.Length)

63
Legacy/Logg/ImageFRM.cs Normal file
View File

@ -0,0 +1,63 @@
//! \file ImageFRM.cs
//! \date 2023 Sep 03
//! \brief Logg image file.
//
// Copyright (C) 2023 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
namespace GameRes.Formats.Logg
{
[Export(typeof(ImageFormat))]
public class FrmFormat : ImageFormat
{
public override string Tag { get => "FRM"; }
public override string Description { get => "Logg image format"; }
public override uint Signature { get => 0x4D5246; } // 'FRM'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
return new ImageMetaData {
Width = header.ToUInt32 (4),
Height = header.ToUInt32 (8),
BPP = 8,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 0x0C;
int stride = file.ReadInt32();
var palette = ReadPalette (file.AsStream);
var pixels = file.ReadBytes (stride * info.iHeight);
return ImageData.Create (info, PixelFormats.Indexed8, palette, pixels, stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("FrmFormat.Write not implemented");
}
}
}

213
Legacy/Omi/ArcDAT.cs Normal file
View File

@ -0,0 +1,213 @@
//! \file ArcDAT.cs
//! \date 2023 Sep 04
//! \brief OMI Script Engine resource archive.
//
// Copyright (C) 2023 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 GameRes.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Text;
namespace GameRes.Formats.Omi
{
[Export(typeof(ArchiveFormat))]
public class DatOpener : ArchiveFormat
{
public override string Tag => "DAT/OMI";
public override string Description => "OMI Script Engine resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
internal const uint DefaultKey = 7654321u;
public DatOpener ()
{
ContainedFormats = new[] { "BMP", "TGA", "WAV", "TXT" };
}
public override ArcFile TryOpen (ArcView file)
{
if (!VFS.IsPathEqualsToFileName (file.Name, "scrdat"))
return null;
using (var input = file.CreateStream())
using (var index = new DecryptedStream (input, DefaultKey, 0))
{
var line = index.ReadLine();
int count = int.Parse (line);
if (!IsSaneCount (count))
return null;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = index.ReadLine();
line = index.ReadLine();
uint size = uint.Parse (line);
var entry = Create<PackedEntry> (name);
entry.Size = size;
entry.IsPacked = entry.Type == "image";
dir.Add (entry);
}
long data_pos = index.Position;
for (int i = 0; i < count; ++i)
{
dir[i].Offset = data_pos;
if (!dir[i].CheckPlacement (file.MaxOffset))
return null;
data_pos += dir[i].Size;
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
Stream input = arc.File.CreateStream (entry.Offset, entry.Size);
input = new DecryptedStream (input, DefaultKey, (uint)entry.Offset);
if (!pent.IsPacked)
return input;
using (var packed = new BinaryStream (input, pent.Name))
{
var unpacked = DecompressRle (packed);
if (pent.UnpackedSize == 0)
pent.UnpackedSize = (uint)unpacked.Length;
return new BinMemoryStream (unpacked, pent.Name);
}
}
internal static byte[] DecompressRle (IBinaryStream input)
{
int size = input.ReadInt32();
var output = new byte[size * 2];
ushort rle_marker = input.ReadUInt16();
int dst = 0;
while (dst < output.Length)
{
input.Read (output, dst, 2);
if (output.ToUInt16 (dst) == rle_marker)
{
input.Read (output, dst, 2);
dst += 2;
int count = input.ReadUInt16() - 1;
if (count > 0)
{
count *= 2;
Binary.CopyOverlapped (output, dst-2, dst, count);
dst += count;
}
}
else
{
dst += 2;
}
}
return output;
}
}
internal class DecryptedStream : InputProxyStream
{
private uint m_key;
static readonly Encoding Encoding = Encodings.cp932;
public override bool CanSeek { get => false; }
public override long Position
{
get => BaseStream.Position;
set => throw new NotSupportedException ("Stream.Position property is not supported");
}
public DecryptedStream (Stream stream, uint key, uint start_offset) : base (stream)
{
if (start_offset > 0)
{
do
{
key = 5 * key - 3;
}
while (--start_offset > 0);
}
m_key = key;
}
public override int Read (byte[] buffer, int offset, int count)
{
int read = BaseStream.Read (buffer, offset, count);
Decrypt (buffer, offset, read);
return read;
}
byte[] m_byte_buffer = new byte[1];
public override int ReadByte ()
{
int b = BaseStream.ReadByte();
if (-1 != b)
{
m_byte_buffer[0] = (byte)b;
Decrypt (m_byte_buffer, 0, 1);
b = m_byte_buffer[0];
}
return b;
}
internal void Decrypt (byte[] data, int offset, int count)
{
for (int i = 0; i < count; ++i)
{
data[offset+i] = (byte)(Binary.RotByteR (data[offset+i], 1) - m_key);
m_key = 5 * m_key - 3;
}
}
byte[] m_buffer;
public string ReadLine ()
{
if (null == m_buffer)
m_buffer = new byte[32];
int size = 0;
for (;;)
{
int b = ReadByte();
if (-1 == b || '\n' == b)
break;
if (m_buffer.Length == size)
{
Array.Resize (ref m_buffer, checked(size/2*3));
}
m_buffer[size++] = (byte)b;
}
return Encoding.GetString (m_buffer, 0, size);
}
public override long Seek (long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
}
}

132
Legacy/Rare/ArcX.cs Normal file
View File

@ -0,0 +1,132 @@
//! \file ArcX.cs
//! \date 2023 Sep 02
//! \brief Rare archive format.
//
// Copyright (C) 2023 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.IO;
// [990528][Rare] Seisen Ren'ai Yuugi
namespace GameRes.Formats.Rare
{
[Export(typeof(ArchiveFormat))]
public class XOpener : ArchiveFormat
{
public override string Tag => "X/RARE";
public override string Description => "Rare resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
static readonly Dictionary<string, Tuple<uint, int>> KnownExeMap = new Dictionary<string, Tuple<uint, int>> {
{ "seisen.exe", Tuple.Create (0x3A9A0u, 715) },
};
public override ArcFile TryOpen (ArcView file)
{
if (!VFS.IsPathEqualsToFileName (file.Name, "PP.X"))
return null;
string full_exe_name = null;
Tuple<uint, int> index_pos = null;
foreach (var exe_name in KnownExeMap.Keys)
{
full_exe_name = VFS.ChangeFileName (file.Name, exe_name);
if (VFS.FileExists (full_exe_name))
{
index_pos = KnownExeMap[exe_name];
break;
}
}
if (null == index_pos)
return null;
uint index_offset = index_pos.Item1;
int count = index_pos.Item2;
using (var index = VFS.OpenView (full_exe_name))
{
index.View.Reserve (index_offset, (uint)count * 12u);
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var entry = new PackedEntry {
Name = string.Format ("PP#{0:D5}.BMP", i),
Type = "image",
Offset = index.View.ReadUInt32 (index_offset),
Size = index.View.ReadUInt32 (index_offset+4),
UnpackedSize = index.View.ReadUInt32 (index_offset+8),
IsPacked = true,
};
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_offset += 12;
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
using (var input = arc.File.CreateStream (entry.Offset, entry.Size))
{
var output = new byte[pent.UnpackedSize];
Decompress (input, output);
return new BinMemoryStream (output, entry.Name);
}
}
internal static void Decompress (IBinaryStream input, byte[] output)
{
var frame = new byte[0x400];
int frame_pos = 1;
int dst = 0;
using (var bits = new MsbBitStream (input.AsStream, true))
{
while (dst < output.Length)
{
int ctl = bits.GetNextBit();
if (-1 == ctl)
break;
if (ctl != 0)
{
int v = bits.GetBits (8);
output[dst++] = frame[frame_pos++ & 0x3FF] = (byte)v;
}
else
{
int offset = bits.GetBits (10);
int count = bits.GetBits (5) + 2;
while (count --> 0)
{
byte v = frame[offset++ & 0x3FF];
output[dst++] = frame[frame_pos++ & 0x3FF] = v;
}
}
}
}
}
}
}

83
Legacy/Rhss/ArcCRG.cs Normal file
View File

@ -0,0 +1,83 @@
//! \file ArcCRG.cs
//! \date 2023 Aug 28
//! \brief Raishū Hyōjun Script System resource archive.
//
// Copyright (C) 2023 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 GameRes.Compression;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Rhss
{
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag => "DAT/CRG";
public override string Description => "RHSS engine resource archive";
public override uint Signature => 0x00475243; // 'CRG'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
uint index_pos = 8;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index_pos+8, 0x30);
var entry = Create<PackedEntry> (name);
entry.Offset = file.View.ReadUInt32 (index_pos);
entry.Size = file.View.ReadUInt32 (index_pos+4);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_pos += 60;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = entry as PackedEntry;
if (null != pent)
{
if (!pent.IsPacked && arc.File.View.AsciiEqual (entry.Offset, "CMP\0"))
{
pent.IsPacked = true;
pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset + 0x4C);
}
if (pent.IsPacked)
{
Stream input = arc.File.CreateStream (entry.Offset+0x50, entry.Size-0x50);
input = new ZLibStream (input, CompressionMode.Decompress);
return new XoredStream (input, 0xFF);
}
}
return base.OpenEntry (arc, entry);
}
}
}

View File

@ -27,6 +27,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows.Media;
// [030817][Splush Wave] Knock Out -Taisengata Datsui Mahjong-
namespace GameRes.Formats.SplushWave namespace GameRes.Formats.SplushWave
{ {
@ -129,5 +132,92 @@ namespace GameRes.Formats.SplushWave
} }
return new BinMemoryStream (output, 0, dst); return new BinMemoryStream (output, 0, dst);
} }
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var fent = (FlkEntry)entry;
var input = BinaryStream.FromStream (OpenEntry (arc, fent), fent.Name);
if ((fent.Flags & 0x10) == 0)
return ImageFormatDecoder.Create (input);
try
{
var info = Swg.ReadMetaData (input) as SwgMetaData;
if (null == info)
{
input.Position = 0;
return new ImageFormatDecoder(input);
}
return new Swg1ImageDecoder (input, info);
}
catch
{
input.Dispose();
throw;
}
}
static readonly ResourceInstance<SwgFormat> s_swg = new ResourceInstance<SwgFormat> ("SWG");
internal static SwgFormat Swg { get => s_swg.Value; }
}
internal sealed class Swg1ImageDecoder : BinaryImageDecoder
{
SwgMetaData m_info;
public Swg1ImageDecoder (IBinaryStream input, SwgMetaData info) : base (input, info)
{
SourceFormat = DatOpener.Swg;
m_info = info;
}
static readonly byte[] PlaneMap = { 3, 2, 1, 0 };
protected override ImageData GetImageData ()
{
m_input.Position = m_info.DataOffset;
int stride = 4 * m_info.iWidth;
int plane_size = m_info.iWidth * m_info.iHeight;
var output = new byte[stride * m_info.iHeight];
ushort[] ctl_buf = new ushort[m_info.iHeight];
for (int c = 0; c < 4; ++c)
{
int compress_method = ReadU16BE();
if (0 == compress_method)
{
int dst = PlaneMap[c] + stride * (m_info.iHeight - 1);
for (int y = 0; y < m_info.iHeight; ++y)
{
for (int x = 0; x < stride; x += 4)
{
output[dst+x] = m_input.ReadUInt8();
}
dst -= stride;
}
continue;
}
if (compress_method != 1)
throw new InvalidFormatException();
for (int y = 0; y < m_info.iHeight; ++y)
{
ctl_buf[y] = ReadU16BE();
}
int row = PlaneMap[c];
for (int y = 0; y < m_info.iHeight; ++y)
{
int dst = row;
int row_size = ctl_buf[y];
SwgFormat.DecompressRow (m_input, row_size, output, dst, 4);
row += stride;
}
}
return ImageData.Create (m_info, PixelFormats.Bgra32, null, output, stride);
}
ushort ReadU16BE ()
{
ushort le = m_input.ReadUInt16();
return (ushort)(le >> 8 | le << 8);
}
} }
} }

View File

@ -68,7 +68,7 @@ namespace GameRes.Formats.SplushWave
{ {
var meta = (SwgMetaData)info; var meta = (SwgMetaData)info;
PixelFormat format = meta.BPP == 8 ? PixelFormats.Indexed8 PixelFormat format = meta.BPP == 8 ? PixelFormats.Indexed8
: meta.BPP == 32 ? PixelFormats.Bgr32 : PixelFormats.Bgr24; : meta.BPP == 32 ? PixelFormats.Bgra32 : PixelFormats.Bgr24;
BitmapPalette palette = null; BitmapPalette palette = null;
if (meta.BPP == 8) if (meta.BPP == 8)
{ {
@ -77,36 +77,40 @@ namespace GameRes.Formats.SplushWave
} }
int stride = meta.iWidth * meta.BPP / 8; int stride = meta.iWidth * meta.BPP / 8;
file.Position = meta.DataOffset; file.Position = meta.DataOffset;
// var pixels = new byte[stride * meta.iHeight]; var pixels = new byte[stride * meta.iHeight];
var pixels = new byte[4 * meta.iWidth * meta.iHeight];
if (!meta.IsCompressed) if (!meta.IsCompressed)
{ {
file.Read (pixels, 0, pixels.Length); file.Read (pixels, 0, pixels.Length);
return ImageData.CreateFlipped (meta, format, palette, pixels, stride); return ImageData.CreateFlipped (meta, format, palette, pixels, stride);
} }
var input = file.ReadBytes ((int)(file.Length - file.Position)); if (!Decompress (file, pixels, meta.Depth + 2, meta.iWidth, meta.iHeight))
if (!Decompress (input, pixels, meta.Depth + 2, meta.iWidth, meta.iHeight))
throw new InvalidFormatException ("Invalid SWG file."); throw new InvalidFormatException ("Invalid SWG file.");
return ImageData.CreateFlipped (meta, format, palette, pixels, stride); return ImageData.CreateFlipped (meta, format, palette, pixels, stride);
} }
bool Decompress (byte[] input, byte[] output, int channels, int width, int height) static readonly byte[] PlaneMap = { 2, 1, 0, 3 };
bool Decompress (IBinaryStream input, byte[] output, int channels, int width, int height)
{ {
int src = 0; long start_pos = input.Position;
if (input[0] != 0 || input[1] != 1) byte hi = input.ReadUInt8();
byte lo = input.ReadUInt8();
if (hi != 0 || lo != 1)
{ {
input.Position = start_pos;
int n = 0; int n = 0;
for (int i = 0; i < channels; ++i) for (int i = 0; i < channels; ++i)
{ {
if (0 == input[i]) if (0 == input.ReadByte())
++n; ++n;
} }
if (n != channels) if (n != channels)
return false; return false;
src = 4; input.Position = start_pos + 4;
hi = input.ReadUInt8();
lo = input.ReadUInt8();
} }
int compress_method = input[src+1] + (input[src] << 8); int compress_method = lo | hi << 8;
src += 2;
if (0 == compress_method) if (0 == compress_method)
{ {
for (int i = 0; i < channels; ++i) for (int i = 0; i < channels; ++i)
@ -115,7 +119,7 @@ namespace GameRes.Formats.SplushWave
int count = height * width; int count = height * width;
while (count --> 0) while (count --> 0)
{ {
output[pos] = input[src++]; output[pos] = input.ReadUInt8();
pos += channels; pos += channels;
} }
} }
@ -123,63 +127,57 @@ namespace GameRes.Formats.SplushWave
} }
if (compress_method != 1) if (compress_method != 1)
return false; return false;
int dst = 0; int stride = width * channels;
int v33 = src; var row_sizes = input.ReadBytes (2 * height * channels);
int v37 = height * channels; int ctl_pos = 0;
src += 2 * v37; for (int c = 0; c < channels; ++c)
for (int row = 0; row < v37; ++row) for (int y = height - 1; y >= 0; --y)
{ {
int y = row % height; int dst = stride * y + PlaneMap[c];
dst = channels * (width * (height - y - 1) + 1) - row / height - 1; int row_size = row_sizes[ctl_pos+1] | row_sizes[ctl_pos] << 8;
if (dst > output.Length) ctl_pos += 2;
return true; DecompressRow (input, row_size, output, dst, channels);
int v24 = 0;
int v36 = input[v33+1] + (input[v33] << 8);
v33 += 2;
do
{
byte lo = input[src];
byte hi = input[src+1];
if (lo != 0)
{
if (lo < 0x81)
{
++src;
int count = lo + 1;
v24 += count + 1;
while (count --> 0)
{
output[dst] = input[src++];
dst += channels;
}
}
else
{
src += 2;
v24 += 2;
int count = Math.Min (0x101 - lo, output.Length - dst);
while (count --> 0)
{
output[dst] = hi;
dst += channels;
}
}
}
else
{
src += 2;
v24 += 2;
output[dst] = hi;
dst += channels;
}
if (dst >= output.Length)
return true;
}
while (v24 < v36);
} }
return true; return true;
} }
internal static void DecompressRow (IBinaryStream input, int row_size, byte[] output, int dst, int step)
{
int x = 0;
while (x < row_size)
{
byte ctl = input.ReadUInt8();
if (ctl == 0)
{
byte v = input.ReadUInt8();
x += 2;
output[dst] = v;
dst += step;
}
else if (ctl < 0x81u)
{
int count = ctl + 1;
x += count + 1;
while (count --> 0)
{
output[dst] = input.ReadUInt8();
dst += step;
}
}
else
{
byte v = input.ReadUInt8();
x += 2;
int count = 0x101 - ctl;
while (count --> 0)
{
output[dst] = v;
dst += step;
}
}
}
}
public override void Write (Stream file, ImageData image) public override void Write (Stream file, ImageData image)
{ {
throw new System.NotImplementedException ("SwgFormat.Write not implemented"); throw new System.NotImplementedException ("SwgFormat.Write not implemented");