From 6ca6dd7e1920e184b1666a37d71a50b524c14123 Mon Sep 17 00:00:00 2001 From: morkt Date: Mon, 8 Aug 2016 17:57:00 +0400 Subject: [PATCH] (VffOpener): recognize archives embedded into EXE files. --- ArcFormats/LiveMaker/ArcVF.cs | 57 ++++++++++++++++++++++++++++------- supported.html | 8 +++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/ArcFormats/LiveMaker/ArcVF.cs b/ArcFormats/LiveMaker/ArcVF.cs index d6ddf7fa..8fec54eb 100644 --- a/ArcFormats/LiveMaker/ArcVF.cs +++ b/ArcFormats/LiveMaker/ArcVF.cs @@ -49,6 +49,7 @@ namespace GameRes.Formats.LiveMaker public override ArcFile TryOpen (ArcView file) { + uint base_offset = 0; ArcView index_file = file; ArcView extra_file = null; try @@ -58,24 +59,32 @@ namespace GameRes.Formats.LiveMaker // game.001 -- [optional] extra part // game.ext -- [optional] separate index (could be included into the main body) - if (!file.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) - return null; uint signature = index_file.View.ReadUInt32 (0); - if (0x666676 != signature) + if (file.Name.EndsWith (".exe", StringComparison.InvariantCultureIgnoreCase) + && (0x5A4D == (signature & 0xFFFF))) // 'MZ' + { + base_offset = SkipExeData (index_file); + signature = index_file.View.ReadUInt32 (base_offset); + } + else if (!file.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) + { + return null; + } + else if (0x666676 != signature) { var ext_filename = Path.ChangeExtension (file.Name, ".ext"); if (!VFS.FileExists (ext_filename)) return null; index_file = VFS.OpenView (ext_filename); signature = index_file.View.ReadUInt32 (0); - if (0x666676 != signature) - return null; } - int count = index_file.View.ReadInt32 (6); + if (0x666676 != signature) + return null; + int count = index_file.View.ReadInt32 (base_offset+6); if (!IsSaneCount (count)) return null; - var dir = ReadIndex (index_file, count); + var dir = ReadIndex (index_file, base_offset, count); if (null == dir) return null; long max_offset = file.MaxOffset; @@ -133,9 +142,9 @@ namespace GameRes.Formats.LiveMaker return input; } - List ReadIndex (ArcView file, int count) + List ReadIndex (ArcView file, uint base_offset, int count) { - uint index_offset = 0xA; + uint index_offset = base_offset+0xA; var name_buffer = new byte[0x100]; var rnd = new TpRandom (0x75D6EE39u); var dir = new List (count); @@ -153,11 +162,11 @@ namespace GameRes.Formats.LiveMaker dir.Add (FormatCatalog.Instance.Create (name)); } rnd.Reset(); - long offset = file.View.ReadInt64 (index_offset) ^ (int)rnd.GetRand32(); + long offset = base_offset + (file.View.ReadInt64 (index_offset) ^ (int)rnd.GetRand32()); foreach (var entry in dir) { index_offset += 8; - long next_offset = file.View.ReadInt64 (index_offset) ^ (int)rnd.GetRand32(); + long next_offset = base_offset + (file.View.ReadInt64 (index_offset) ^ (int)rnd.GetRand32()); entry.Offset = offset; entry.Size = (uint)(next_offset - offset); offset = next_offset; @@ -178,6 +187,31 @@ namespace GameRes.Formats.LiveMaker } return Encodings.cp932.GetString (name_buf, 0, name_length); } + + uint SkipExeData (ArcView file) + { + uint offset = 0; + uint pe_offset = file.View.ReadUInt32 (0x3c); + if (pe_offset < file.MaxOffset && 0x4550 == file.View.ReadUInt32 (pe_offset)) // 'PE' + { + int opt_header = file.View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader + offset = file.View.ReadUInt32 (pe_offset+0x54); // SizeOfHeaders + long section_table = pe_offset+opt_header+0x18; + int count = file.View.ReadUInt16 (pe_offset+6); // NumberOfSections + if (section_table + 0x28*count < file.MaxOffset) + { + for (int i = 0; i < count; ++i) + { + uint size = file.View.ReadUInt32 (section_table+0x10); + uint addr = file.View.ReadUInt32 (section_table+0x14); + section_table += 0x28; + if (0 != size) + offset = Math.Max (addr + size, offset); + } + } + } + return offset; + } } internal class TpRandom @@ -239,6 +273,7 @@ namespace GameRes.Formats.LiveMaker ExtraFile.Dispose(); _vff_disposed = true; } + base.Dispose (disposing); } } } diff --git a/supported.html b/supported.html index fd153205..6fc8ce97 100644 --- a/supported.html +++ b/supported.html @@ -101,6 +101,7 @@ Okaa-san ga Ippai!
*+*.lst-NoNexton LikeC Chikan Ou ~Inkoku no Souzousha~
Daisaimin Rankou Gakuen
+Haitoku no Gakuen ~Yami ni Sasagerareta Otome-tachi~
Injoku Shinryuu Club
Moon.
Ryoujoku Famiresu Choukyou Menu
@@ -255,6 +256,7 @@ Oku-sama wa Moto Yariman
Omana 2: Omaenchi Moeteruzo
Ore no Saimin Fantasia
Ouka Ryouran
+Oyako Ninjutsu Kunoichi PonPon!!
RGH ~Koi to Hero to Gakuen to~
Riding Incubus
Seirei Tenshou
@@ -294,6 +296,7 @@ Natsu Kagura
Assault Angel Canon
Chikatetsu Fuusa Jiken
Eien no Owari ni
+Elf no Futago Hime Willan to Arsura
Fuurinkanzan
Gedou Yuusha
Himemiko
@@ -951,6 +954,7 @@ Big Magnum Harimoto-sensei
*.mfcMFCNo game.datvffNoLiveMaker Grope ~Yami no Naka no Kotori-tachi~
+Incest Lover ~Mama to Haha no Himitsu~
Ryoujoku Seifuku Jogakuen ~Chimitsu ni Nureta Seifuku~
*.galGale105
Gale106No @@ -987,8 +991,11 @@ Hanamaru! 2
Hime Kami 1/2
In'youchuu Goku ~Ryoujoku Jigoku Taimaroku~
In'youchuu Rei ~Ryoujoku Shiro Taima Emaki~
+Kuraibito
+Onna Kyoushi Suzune
Oshioki ~Gakuen Reijou Kousei Keikaku~
Ore Maou! ~Kudake Chitta Tamashii
+Volley Coaching!
Zoku Etsuraku no Tane
*.binOZNoPatisserie @@ -1017,6 +1024,7 @@ Rakuin Hime Runed Princess
Elevator Panic ~Misshitsu no Inkou~
*.datMK2.0NoMAIKA +Inka Gakuen Taisen
Uchuu Keiji Soldivan
*.ttd.FRCNoMorning