diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index dc2833d2..00e64f3f 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -98,6 +98,12 @@ + + + + WidgetNCARC.xaml + + @@ -293,6 +299,16 @@ + + + Code + + + + + Code + + @@ -518,6 +534,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -615,7 +635,6 @@ - perl "$(SolutionDir)inc-revision.pl" "$(ProjectPath)" $(ConfigurationName) diff --git a/ArcFormats/NonColor/ArcDAT.cs b/ArcFormats/NonColor/ArcDAT.cs new file mode 100644 index 00000000..cd0415a4 --- /dev/null +++ b/ArcFormats/NonColor/ArcDAT.cs @@ -0,0 +1,280 @@ +//! \file ArcDAT.cs +//! \date Sat May 14 02:20:37 2016 +//! \brief 'non color' resource archive. +// +// Copyright (C) 2016 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; +using GameRes.Compression; +using GameRes.Utility; +using GameRes.Formats.Properties; +using GameRes.Formats.Strings; + +namespace GameRes.Formats.NonColor +{ + internal class ArcDatEntry : PackedEntry + { + public byte[] RawName; + public ulong Hash; + public int Flags; + } + + internal class ArcDatArchive : ArcFile + { + public readonly ulong MasterKey; + + public ArcDatArchive (ArcView arc, ArchiveFormat impl, ICollection dir, ulong key) + : base (arc, impl, dir) + { + MasterKey = key; + } + } + + [Serializable] + public class Scheme + { + public string Title; + public ulong Hash; + + public Scheme(string title) + { + Title = title; + var key = Encodings.cp932.GetBytes(title); + Hash = Crc64.Compute(key, 0, key.Length); + } + } + + [Serializable] + public class ArcDatScheme : ResourceScheme + { + public Dictionary KnownSchemes; + + public static ulong GetKey (string title) + { + var key = Encodings.cp932.GetBytes (title); + return Crc64.Compute (key, 0, key.Length); + } + } + + public class ArcDatOptions : ResourceOptions + { + public string Scheme; + } + + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag { get { return "ARC/noncolor"; } } + public override string Description { get { return "'non color' resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return false; } } + + public DatOpener () + { + Extensions = new string[] { "dat" }; + } + + public static readonly string PersistentFileMapName = "NCFileMap.dat"; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) + return null; + int count = file.View.ReadInt32 (0) ^ 0x26ACA46E; + if (!IsSaneCount (count)) + return null; + + var scheme = QueryScheme(); + if (null == scheme) + return null; + var file_map = ReadFilenameMap (scheme); + + uint index_offset = 4; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + ulong hash = file.View.ReadUInt64 (index_offset); + int flags = file.View.ReadByte (index_offset+8) ^ (byte)hash; + uint offset = file.View.ReadUInt32 (index_offset+9) ^ (uint)hash; + uint size = file.View.ReadUInt32 (index_offset+0xD) ^ (uint)hash; + uint unpacked_size = file.View.ReadUInt32 (index_offset+0x11) ^ (uint)hash; + index_offset += 0x15; + + string name; + byte[] raw_name = null; + if (file_map.TryGetValue (hash, out raw_name)) + { + name = Encodings.cp932.GetString (raw_name); + } + else if (2 == flags) + { + name = hash.ToString ("X8"); + } + else + { + continue; + } + if (flags != 2) + { + offset ^= raw_name[raw_name.Length >> 1]; + size ^= raw_name[raw_name.Length >> 2]; + unpacked_size ^= raw_name[raw_name.Length >> 3]; + } + var entry = new ArcDatEntry { + Name = name, + Type = FormatCatalog.Instance.GetTypeFromName (name), + RawName = raw_name, + Hash = hash, + Flags = flags, + Offset = offset, + Size = size, + UnpackedSize = unpacked_size, + IsPacked = 2 == flags, + }; + + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + return new ArcDatArchive (file, this, dir, scheme.Hash); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var darc = arc as ArcDatArchive; + var dent = entry as ArcDatEntry; + if (null == darc || null == dent || 0 == dent.Flags || 0 == dent.Size) + return base.OpenEntry (arc, entry); + var data = arc.File.View.ReadBytes (entry.Offset, entry.Size); + if (2 == dent.Flags) + { + if (darc.MasterKey != 0) + { + unsafe + { + fixed (byte* data8 = data) + { + uint key = (uint)(dent.Hash ^ darc.MasterKey); + uint* data32 = (uint*)data8; + for (int i = data.Length/4; i > 0; --i) + *data32++ ^= key; + } + } + } + return new ZLibStream (new MemoryStream (data), CompressionMode.Decompress); + } + // 1 == dent.Flags + int block_length = data.Length / dent.RawName.Length; + int n = 0; + for (int i = 0; i < dent.RawName.Length-1; ++i) + for (int j = 0; j < block_length; ++j) + data[n++] ^= dent.RawName[i]; + return new MemoryStream (data); + } + + static IDictionary> FileMapIndex = null; + + internal IDictionary> ReadIndex (BinaryReader idx) + { + int scheme_count = idx.ReadInt32(); + idx.BaseStream.Seek (12, SeekOrigin.Current); + var map = new Dictionary> (scheme_count); + for (int i = 0; i < scheme_count; ++i) + { + ulong key = idx.ReadUInt64(); + uint offset = idx.ReadUInt32(); + int count = idx.ReadInt32(); + map[key] = Tuple.Create (offset, count); + } + return map; + } + + Tuple> LastAccessedScheme; + + internal IDictionary ReadFilenameMap (Scheme scheme) + { + if (null != LastAccessedScheme && LastAccessedScheme.Item1 == scheme.Hash) + return LastAccessedScheme.Item2; + var dir = Path.GetDirectoryName (System.Reflection.Assembly.GetExecutingAssembly().Location); + var lst_file = Path.Combine (dir, PersistentFileMapName); + var idx_file = Path.ChangeExtension (lst_file, ".idx"); + using (var idx_stream = File.OpenRead (idx_file)) + using (var idx = new BinaryReader (idx_stream)) + { + if (null == FileMapIndex) + FileMapIndex = ReadIndex (idx); + + Tuple nc_info; + if (!FileMapIndex.TryGetValue (scheme.Hash, out nc_info)) + throw new UnknownEncryptionScheme(); + + using (var lst_stream = File.OpenRead (lst_file)) + using (var lst = new BinaryReader (lst_stream)) + { + var name_map = new Dictionary (nc_info.Item2); + idx_stream.Position = nc_info.Item1; + for (int i = 0; i < nc_info.Item2; ++i) + { + ulong key = idx.ReadUInt64(); + uint offset = idx.ReadUInt32(); + int length = idx.ReadInt32(); + lst_stream.Position = offset; + name_map[key] = lst.ReadBytes (length); + } + LastAccessedScheme = Tuple.Create (scheme.Hash, name_map); + return name_map; + } + } + } + + public static Dictionary KnownSchemes = new Dictionary(); + + public override ResourceScheme Scheme + { + get { return new ArcDatScheme { KnownSchemes = KnownSchemes }; } + set { KnownSchemes = ((ArcDatScheme)value).KnownSchemes; } + } + + Scheme QueryScheme () + { + var options = Query (arcStrings.ArcEncryptedNotice); + Scheme scheme; + if (string.IsNullOrEmpty (options.Scheme) || !KnownSchemes.TryGetValue (options.Scheme, out scheme)) + return null; + return scheme; + } + + public override ResourceOptions GetDefaultOptions () + { + return new ArcDatOptions { Scheme = Settings.Default.NCARCScheme }; + } + + public override object GetAccessWidget () + { + return new GUI.WidgetNCARC(); + } + } +} diff --git a/ArcFormats/NonColor/WidgetNCARC.xaml b/ArcFormats/NonColor/WidgetNCARC.xaml new file mode 100644 index 00000000..520a23d8 --- /dev/null +++ b/ArcFormats/NonColor/WidgetNCARC.xaml @@ -0,0 +1,12 @@ + + + + diff --git a/ArcFormats/NonColor/WidgetNCARC.xaml.cs b/ArcFormats/NonColor/WidgetNCARC.xaml.cs new file mode 100644 index 00000000..cd04ca98 --- /dev/null +++ b/ArcFormats/NonColor/WidgetNCARC.xaml.cs @@ -0,0 +1,18 @@ +using System.Windows.Controls; +using System.Linq; +using GameRes.Formats.NonColor; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetNCARC.xaml + /// + public partial class WidgetNCARC : StackPanel + { + public WidgetNCARC() + { + InitializeComponent(); + Scheme.ItemsSource = DatOpener.KnownSchemes.OrderBy (x => x.Key); + } + } +} diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index 20d22d5b..f1312dd4 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -525,5 +525,17 @@ namespace GameRes.Formats.Properties { this["AGSTitle"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string NCARCScheme { + get { + return ((string)(this["NCARCScheme"])); + } + set { + this["NCARCScheme"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index 7dd944dd..07de4ed3 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -128,5 +128,8 @@ + + + \ No newline at end of file diff --git a/ArcFormats/app.config b/ArcFormats/app.config index 02fbcabf..c4096b3a 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -130,6 +130,9 @@ + + + diff --git a/supported.html b/supported.html index afca58c3..1bf32f74 100644 --- a/supported.html +++ b/supported.html @@ -108,6 +108,7 @@ Ai Suru Tsuma, Misaki no Furin Shouko
Amber Quartz
Arpeggio ~Kimi Iro no Melody~
Chikan Kizoku
+Futagoza no Paradox
Narimono
Reconquista
White ~blanche comme la lune~
@@ -155,6 +156,7 @@ Umineko
*.pacPACNoNeXAS Baldr Sky DiveX
+Fossette ~Cafe au Le Ciel Bleu~
Jinki Extend Re:Vision
Maji de Watashi ni Koishinasai!
@@ -193,6 +195,7 @@ Fairytale Requiem
Love Negotiator
Sekien no Inganock
Shikkoku no Sharnoth
+Yami no Sen Ou ~Seijo Ojoku~
*.lwgLGNo *.wcgWGqYes @@ -218,10 +221,12 @@ Mayoeru Futari to Sekai no Subete
Mahoutsukai no Yoru
Nakadashi Hara Maid series
Natsupochi
+Natsuzora Kanata
Nidaime wa ☆ Mahou Shoujo
Nuki Doki!
Okiba ga Nai!
Oku-sama wa Moto Yariman
+Omana 2: Omaenchi Moeteruzo
Ore no Saimin Fantasia
RGH ~Koi to Hero to Gakuen to~
Riding Incubus
@@ -265,6 +270,7 @@ Shoujo Senki Soul Eater
*.dat-NoM no VioletNanase Ren *gra
mas
difNo *.ald-NoAlice Soft +Pastel Chime 3 Bind Seeker
Shaman's Sanctuary -Miko no Seiiki-
Tsuma Shibori
@@ -293,6 +299,7 @@ Mechiku ~Injoku no Shuuyoujo~
Momo x Momi
Michibikareshi Mono-tachi no Rakuen ~BEDLAM~
Onsen Kankou Yukemuri Chijou
+Oshiete! Bloomer Sensei
Ryoujoku Costume Play
Seikoujo Claudia
Taijoku no Ori
@@ -408,6 +415,7 @@ Itazura ZERO
*.noa
*.datEntis\x1aNoEntis GLS Alea Akaki Tsuki o Haruka ni Nozomi
Iroha ~Aki no Yuuhi ni Kagefumi o~
+Gap P
Konneko
Natsu no Ame
Nerawareta Megami Tenshi Angeltia
@@ -441,6 +449,7 @@ X Change 2
X Change 2R
Eve to Iu Na no Omocha
Hissatsu Chikannin II
+Otomegari
Ryoujoku Gojuusou
Tokumei Sentai Sirenger
Tokumei Sentai Yuzu Ranger
@@ -452,6 +461,7 @@ Zetsuboushi
*.zbm
*.cwlSZDDNo *.packFilePackVer1.0
FilePackVer2.0
FilePackVer3.0NoQLIE Bishoujo Mangekyou -Kami ga Tsukuritamouta Shoujo-tachi-
+Hidamari Basket
Makai Tenshi Djibril -episode 3-
Mehime no Toriko
Nanatsu no Fushigi no Owaru Toki
@@ -546,7 +556,8 @@ Vampire Crusaders
*.gpk+*.gtb
*.vpk+*.vtb-NoBlack Cyc Before Dawn Daybreak ~Shinen no Utahime~
Gun-Katana
-Yami no Koe Zero
+Kurogane no Tsubasa ~The Alchemist's Story~
+Yami no Koe series
*.dwqBMP
JPEG
PNG
JPEG+MASK
PACKBMP+MASK
No *.vaw
*.wgqIF PACKTYPE==
OGGNo @@ -629,6 +640,7 @@ Shukufuku no Kane no Oto wa, Sakurairo no Kaze to Tomo ni
*.bsaBSArcNoBishop Houkago ~Nureta Seifuku~
+Kyouiku Shidou
Yakata ~Kannou Kitan~
*.bsgBSS-Graphics
BSS-CompositionNo @@ -698,6 +710,7 @@ Haruiro Ouse
*.pb3PB3BNo *.g2
*.stx-NoGLib2 +Hikikomori Muke Hitozuma-sensei
Hitozuma Net Auction
*.pgxPGXNo @@ -717,17 +730,22 @@ Paimega
*.snn+*.inx-NoBlueGale Cafe Junkie
+Immoral
Majidashi! Royale ~Finish wa Watashi no Naka de~
MILK Junkies
*.zbmamp_No *.vfsVFNoAoi +Alfred Gakuen Mamono Daitai
+Brown Doori Sanbanme
+Daisounan
Dancing Crazies
Level Justice
+Ouzoku
*.iphRIFF....IPH fmtNo *.aogAoiOggNo -*.boxAOIBX10
AOIBOX7No +*.boxAOIBOX5
AOIBOX7
AOIBX10
AOIBX12
AOIMY01No *.gd+*.dll-NoXuse Eien no Aselia -The Spirit of Eternity Sword- @@ -777,12 +795,14 @@ Angenehm Platz -Kleiner Garten Sie Erstellen-
*.emeRREDATANoEmon Engine Ase Nure Shoujo Misaki "Anata no Nioi de Icchau!"
+D-spray Biyaku de Motemote Kachou Dairi Hosa
Hitomi no Rakuin ~Inbaku no Mesu Dorei~
*.grp-NoAnkh Mozu
*.gpcGpc7NoSuper NekoX +Jorou Gumo ~Makotogatari~
Sister Contrast! *.psbPSBNoE-mote @@ -792,6 +812,7 @@ Angenehm Platz -Kleiner Garten Sie Erstellen-
Sweet ~Hanjuku na Tenshi-tachi~
*.ovk-NoRealLive +Gakuen Taima! Holy x Moly
Joi-san no Iitsuke!!
Shiawase na Ohime-sama
@@ -802,6 +823,12 @@ Shiawase na Ohime-sama
Valkyrie Complex
*.cps-No +arc*.dat
script.dat-Nonon color +Doubly na Kanojo
+Nora to Oujo to Noraneko Heart
+Tsujidou-san no Jun'ai Road
+Tsujidou-san no Virgin Road
+

1 Non-encrypted only