implemented "MCG 1.01" images.

This commit is contained in:
morkt 2015-11-23 21:31:10 +04:00
parent bcc3583885
commit 0924d385e8
5 changed files with 225 additions and 27 deletions

View File

@ -68,6 +68,9 @@
<Compile Include="AnimeGameSystem\AudioPCM.cs" /> <Compile Include="AnimeGameSystem\AudioPCM.cs" />
<Compile Include="Eushully\ArcGPC.cs" /> <Compile Include="Eushully\ArcGPC.cs" />
<Compile Include="Eushully\ImageGP.cs" /> <Compile Include="Eushully\ImageGP.cs" />
<Compile Include="FC01\WidgetMCG.xaml.cs">
<DependentUpon>WidgetMCG.xaml</DependentUpon>
</Compile>
<Compile Include="ImagePSD.cs" /> <Compile Include="ImagePSD.cs" />
<Compile Include="Propeller\ArcMGR.cs" /> <Compile Include="Propeller\ArcMGR.cs" />
<Compile Include="Propeller\ArcMPK.cs" /> <Compile Include="Propeller\ArcMPK.cs" />
@ -394,6 +397,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="FC01\WidgetMCG.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="NitroPlus\CreateNPAWidget.xaml"> <Page Include="NitroPlus\CreateNPAWidget.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@ -29,6 +29,7 @@ using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows.Media; using System.Windows.Media;
using GameRes.Formats.Properties; using GameRes.Formats.Properties;
using GameRes.Formats.Strings;
using GameRes.Utility; using GameRes.Utility;
namespace GameRes.Formats.FC01 namespace GameRes.Formats.FC01
@ -37,6 +38,18 @@ namespace GameRes.Formats.FC01
{ {
public int DataOffset; public int DataOffset;
public int PackedSize; public int PackedSize;
public int Version;
}
internal class McgOptions : ResourceOptions
{
public byte Key;
}
[Serializable]
public class McgScheme : ResourceScheme
{
public Dictionary<string, byte> KnownKeys;
} }
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
@ -46,12 +59,23 @@ namespace GameRes.Formats.FC01
public override string Description { get { return "F&C Co. image format"; } } public override string Description { get { return "F&C Co. image format"; } }
public override uint Signature { get { return 0x2047434D; } } // 'MCG' public override uint Signature { get { return 0x2047434D; } } // 'MCG'
public static Dictionary<string, byte> KnownKeys = new Dictionary<string, byte>();
public override ResourceScheme Scheme
{
get { return new McgScheme { KnownKeys = KnownKeys }; }
set { KnownKeys = ((McgScheme)value).KnownKeys; }
}
public override ImageMetaData ReadMetaData (Stream stream) public override ImageMetaData ReadMetaData (Stream stream)
{ {
byte[] header = new byte[0x40]; byte[] header = new byte[0x40];
if (header.Length != stream.Read (header, 0, header.Length)) if (header.Length != stream.Read (header, 0, header.Length))
return null; return null;
if (!Binary.AsciiEqual (header, 4, "2.00")) if (header[5] != '.')
return null;
int version = header[4] * 100 + header[6] * 10 + header[7] - 0x14D0;
if (version != 200 && version != 101)
throw new NotSupportedException ("Not supported MCG format version"); throw new NotSupportedException ("Not supported MCG format version");
int header_size = LittleEndian.ToInt32 (header, 0x10); int header_size = LittleEndian.ToInt32 (header, 0x10);
if (header_size < 0x40) if (header_size < 0x40)
@ -68,18 +92,32 @@ namespace GameRes.Formats.FC01
BPP = bpp, BPP = bpp,
DataOffset = header_size, DataOffset = header_size,
PackedSize = LittleEndian.ToInt32 (header, 0x38) - header_size, PackedSize = LittleEndian.ToInt32 (header, 0x38) - header_size,
Version = version,
}; };
} }
// cache key value so that dialog does not pop up on every file accessed.
byte? LastKey = null;
public override ImageData Read (Stream stream, ImageMetaData info) public override ImageData Read (Stream stream, ImageMetaData info)
{ {
var meta = info as McgMetaData; var meta = (McgMetaData)info;
if (null == meta) byte key = Settings.Default.MCGLastKey;
throw new ArgumentException ("McgFormat.Read should be supplied with McgMetaData", "info"); if (101 == meta.Version)
{
var reader = new McgDecoder (stream, meta); if (null == LastKey)
{
var options = Query<McgOptions> (arcStrings.ArcImageEncrypted);
key = options.Key;
}
else
key = LastKey.Value;
}
var reader = new McgDecoder (stream, meta, key);
reader.Unpack(); reader.Unpack();
return ImageData.Create (info, PixelFormats.Bgr24, null, reader.Data); if (reader.Key != 0)
LastKey = reader.Key;
return ImageData.Create (info, PixelFormats.Bgr24, null, reader.Data, reader.Stride);
} }
public override void Write (Stream file, ImageData image) public override void Write (Stream file, ImageData image)
@ -87,10 +125,23 @@ namespace GameRes.Formats.FC01
throw new System.NotImplementedException ("McgFormat.Write not implemented"); throw new System.NotImplementedException ("McgFormat.Write not implemented");
} }
public static readonly IReadOnlyDictionary<string, byte> KnownKeys = new Dictionary<string, byte>() public override ResourceOptions GetDefaultOptions ()
{ {
{ "Konata yori Kanata made", 0xD5 }, return new McgOptions { Key = Settings.Default.MCGLastKey };
}; }
public override ResourceOptions GetOptions (object widget)
{
var w = widget as GUI.WidgetMCG;
if (null != w)
Settings.Default.MCGLastKey = w.GetKey();
return GetDefaultOptions();
}
public override object GetAccessWidget ()
{
return new GUI.WidgetMCG();
}
} }
// mcg decompression // graphic.unt @ 100047B0 // mcg decompression // graphic.unt @ 100047B0
@ -99,31 +150,65 @@ namespace GameRes.Formats.FC01
{ {
byte[] m_input; byte[] m_input;
byte[] m_output; byte[] m_output;
uint m_width; int m_width;
uint m_height; int m_height;
uint m_pixels; int m_pixels;
byte m_key; byte m_key;
int m_version;
public byte[] Data { get { return m_output; } } public byte[] Data { get { return m_output; } }
public int Stride { get; private set; }
public byte Key { get { return m_key; } }
public McgDecoder (Stream input, McgMetaData info) public McgDecoder (Stream input, McgMetaData info, byte key)
{ {
input.Position = info.DataOffset; input.Position = info.DataOffset;
m_input = new byte[info.PackedSize]; m_input = new byte[info.PackedSize];
if (m_input.Length != input.Read (m_input, 0, m_input.Length)) if (m_input.Length != input.Read (m_input, 0, m_input.Length))
throw new InvalidFormatException ("Unexpected end of file"); throw new InvalidFormatException ("Unexpected end of file");
m_width = info.Width; m_width = (int)info.Width;
m_height = info.Height; m_height = (int)info.Height;
m_pixels = m_width*m_height; m_pixels = m_width*m_height;
m_output = new byte[m_pixels*3]; m_key = key;
m_key = Settings.Default.MCGLastKey; m_version = info.Version;
Stride = 3 * m_width;
if (101 == m_version)
Stride = (Stride + 3) & -4;
} }
static readonly byte[] ChannelOrder = { 1, 0, 2 }; static readonly byte[] ChannelOrder = { 1, 0, 2 };
public void Unpack () public void Unpack ()
{ {
var reader = new MrgDecoder (m_input, 0, m_pixels); if (200 == m_version)
UnpackV200();
else
UnpackV101();
}
void UnpackV101 ()
{
if (m_key != 0)
{
MrgOpener.Decrypt (m_input, 0, m_input.Length-1, m_key);
}
using (var input = new MemoryStream (m_input))
using (var lzss = new MrgLzssReader (input, m_input.Length, Stride * m_height))
{
lzss.Unpack();
// data remaining within input stream indicates invalid encryption key
if (input.Length - input.Position > 1)
{
m_key = 0;
}
m_output = lzss.Data;
}
}
void UnpackV200 ()
{
m_output = new byte[m_pixels*3];
var reader = new MrgDecoder (m_input, 0, (uint)m_pixels);
do do
{ {
reader.ResetKey (m_key); reader.ResetKey (m_key);
@ -156,27 +241,26 @@ namespace GameRes.Formats.FC01
void Transform () void Transform ()
{ {
uint dst = 0; int dst = 0;
uint stride = (m_width - 1) * 3; for (int y = m_height-1; y > 0; --y) // @@1a
for (uint y = m_height-1; y > 0; --y) // @@1a
{ {
for (uint x = stride; x > 0; --x) // @@1b for (int x = Stride-3; x > 0; --x) // @@1b
{ {
int p0 = m_output[dst]; int p0 = m_output[dst];
int py = m_output[dst+stride+3] - p0; int py = m_output[dst+Stride] - p0;
int px = m_output[dst+3] - p0; int px = m_output[dst+3] - p0;
p0 = Math.Abs (px + py); p0 = Math.Abs (px + py);
py = Math.Abs (py); py = Math.Abs (py);
px = Math.Abs (px); px = Math.Abs (px);
byte pv; byte pv;
if (p0 >= px && py >= px) if (p0 >= px && py >= px)
pv = m_output[dst+stride+3]; pv = m_output[dst+Stride];
else if (p0 < py) else if (p0 < py)
pv = m_output[dst]; pv = m_output[dst];
else else
pv = m_output[dst+3]; pv = m_output[dst+3];
m_output[dst+stride+6] += (byte)(pv + 0x80); m_output[dst+Stride+3] += (byte)(pv + 0x80);
++dst; ++dst;
} }
dst += 3; dst += 3;

View File

@ -0,0 +1,26 @@
<StackPanel x:Class="GameRes.Formats.GUI.WidgetMCG"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:GameRes.Formats.Strings"
xmlns:p="clr-namespace:GameRes.Formats.Properties"
xmlns:gui="clr-namespace:GameRes.Formats.GUI"
MaxWidth="250" Orientation="Vertical">
<StackPanel.Resources>
<gui:ByteToStringConverter x:Key="byteKeyConverter" />
<gui:StringToByteConverter x:Key="stringKeyConverter" />
</StackPanel.Resources>
<Label Content="{x:Static s:arcStrings.MCGChoose}" Target="{Binding ElementName=Title}" HorizontalAlignment="Left" Padding="0,0,0,10"/>
<ComboBox Name="Title" ItemsSource="{Binding}"
SelectedValue="{Binding ElementName=Passkey, Path=Text, Mode=TwoWay, Converter={StaticResource stringKeyConverter}}"
SelectedValuePath="Value" DisplayMemberPath="Key"
Width="200" HorizontalAlignment="Left"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition MinWidth="50" Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{x:Static s:arcStrings.MCGLabelKey}" Target="{Binding ElementName=Passkey}" ToolTip="{x:Static s:arcStrings.TooltipHex}" Grid.Column="0" Grid.Row="0" Padding="0,1" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Name="Passkey" Text="{Binding Source={x:Static p:Settings.Default}, Path=MCGLastKey, Mode=TwoWay, Converter={StaticResource byteKeyConverter}}"
ToolTip="{x:Static s:arcStrings.TooltipHex}" Margin="5" Width="50" Grid.Column="1" Grid.Row="0"/>
</Grid>
</StackPanel>

View File

@ -0,0 +1,77 @@
using GameRes.Formats.FC01;
using GameRes.Formats.Strings;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Data;
namespace GameRes.Formats.GUI
{
/// <summary>
/// Interaction logic for WidgetMCG.xaml
/// </summary>
public partial class WidgetMCG : StackPanel
{
public WidgetMCG ()
{
InitializeComponent();
var none = new KeyValuePair<string, byte>[] { new KeyValuePair<string, byte> (arcStrings.ArcIgnoreEncryption, 0) };
Title.ItemsSource = none.Concat (McgFormat.KnownKeys.OrderBy (x => x.Key));
}
public byte GetKey ()
{
return ByteKeyConverter.StringToByte (this.Passkey.Text);
}
}
internal static class ByteKeyConverter
{
public static string ByteToString (object value)
{
byte key = (byte)value;
return key.ToString ("X2", CultureInfo.InvariantCulture);
}
public static byte StringToByte (object value)
{
string s = value as string;
if (string.IsNullOrEmpty (s))
return 0;
byte key;
if (!byte.TryParse (s, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out key))
return 0;
return key;
}
}
[ValueConversion (typeof (byte), typeof (string))]
class ByteToStringConverter : IValueConverter
{
public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
{
return ByteKeyConverter.ByteToString (value);
}
public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
{
return ByteKeyConverter.StringToByte (value);
}
}
[ValueConversion (typeof (string), typeof (byte))]
class StringToByteConverter : IValueConverter
{
public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
{
return ByteKeyConverter.StringToByte (value);
}
public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
{
return ByteKeyConverter.ByteToString (value);
}
}
}

View File

@ -376,7 +376,10 @@ Yuukyou Gangu 2<br/>
<tr class="odd"><td>*.wbm</td><td><tt>WPX\x1ABMP</tt></td><td>No</td></tr> <tr class="odd"><td>*.wbm</td><td><tt>WPX\x1ABMP</tt></td><td>No</td></tr>
<tr class="odd"><td>*.wpn</td><td><tt>WBD\x1AWAV</tt></td><td>No</td></tr> <tr class="odd"><td>*.wpn</td><td><tt>WBD\x1AWAV</tt></td><td>No</td></tr>
<tr class="odd"><td>*.wwa</td><td><tt>WPX\x1AWAV</tt></td><td>No</td></tr> <tr class="odd"><td>*.wwa</td><td><tt>WPX\x1AWAV</tt></td><td>No</td></tr>
<tr><td>*.mrg</td><td><tt>MRG</tt></td><td>No</td><td rowspan="3">F&amp;C</td><td rowspan="3">Konata yori Kanata made</td></tr> <tr><td>*.mrg</td><td><tt>MRG</tt></td><td>No</td><td rowspan="3">F&amp;C</td><td rowspan="3">
Guren ni Somaru Gin no Rozario<br/>
Konata yori Kanata made<br/>
</td></tr>
<tr><td>*.mcg</td><td><tt>MCG 2.00</tt></td><td>No</td></tr> <tr><td>*.mcg</td><td><tt>MCG 2.00</tt></td><td>No</td></tr>
<tr><td>*.acd</td><td><tt>ACD 1.00</tt></td><td>No</td></tr> <tr><td>*.acd</td><td><tt>ACD 1.00</tt></td><td>No</td></tr>
<tr class="odd"><td>*.pk<br/>*.gpk<br/>*.tpk<br/>*.wpk<br/></td><td>-</td><td>No</td><td rowspan="2">U-Me Soft</td><td rowspan="2"> <tr class="odd"><td>*.pk<br/>*.gpk<br/>*.tpk<br/>*.wpk<br/></td><td>-</td><td>No</td><td rowspan="2">U-Me Soft</td><td rowspan="2">
@ -417,6 +420,7 @@ Nega0<br/>
</td></tr> </td></tr>
<tr><td>*.wag<br/>*.4ag</td><td><tt>WAG@</tt><br/><tt>GAF4</tt></td><td>No</td></tr> <tr><td>*.wag<br/>*.4ag</td><td><tt>WAG@</tt><br/><tt>GAF4</tt></td><td>No</td></tr>
<tr class="odd"><td>*.ykc</td><td><tt>YKC001</tt></td><td>Yes</td><td rowspan="2">Yuka</td><td rowspan="2"> <tr class="odd"><td>*.ykc</td><td><tt>YKC001</tt></td><td>Yes</td><td rowspan="2">Yuka</td><td rowspan="2">
Aozora no Mieru Oka<br/>
PriministAr<br/> PriministAr<br/>
SuGirly Wish<br/> SuGirly Wish<br/>
_summer<br/> _summer<br/>