mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-23 19:34:15 +08:00
implemented "MCG 1.01" images.
This commit is contained in:
parent
bcc3583885
commit
0924d385e8
@ -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>
|
||||||
|
@ -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;
|
||||||
|
26
ArcFormats/FC01/WidgetMCG.xaml
Normal file
26
ArcFormats/FC01/WidgetMCG.xaml
Normal 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>
|
77
ArcFormats/FC01/WidgetMCG.xaml.cs
Normal file
77
ArcFormats/FC01/WidgetMCG.xaml.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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&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&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/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user