From 04cce56ab803a3c15069e166a4d9e665407065ac Mon Sep 17 00:00:00 2001 From: zzwbdz <47297257+zzwbdz@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:41:55 +0800 Subject: [PATCH] Add Repack support for ELF (.ArcAi6win) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only tested on the ELF's Ai6win engine game "媚肉の香り," the repackaged .arc files can be read normally by the game, theoretically supporting all ELF's Ai6win engine games. But it may not support .arc files of Silky and Silky Plus. Silky and Silky Plus are derivative brands of ELF, ELF has gone bankrupt, but Silky Plus has survived to this day. Games from the three brands share a set of unpacking logic, but it is not yet clear whether Silky and Silky Plus have encrypted .arc files afterward. --- ArcFormats/Silky/ArcAi6Win.cs | 156 ++++++++++++++++++++++++++++------ 1 file changed, 131 insertions(+), 25 deletions(-) diff --git a/ArcFormats/Silky/ArcAi6Win.cs b/ArcFormats/Silky/ArcAi6Win.cs index 72e8269e1..77f5c06e1 100644 --- a/ArcFormats/Silky/ArcAi6Win.cs +++ b/ArcFormats/Silky/ArcAi6Win.cs @@ -27,7 +27,9 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Linq; using GameRes.Compression; +using GameRes.Formats.Strings; using GameRes.Utility; namespace GameRes.Formats.Silky @@ -35,66 +37,170 @@ namespace GameRes.Formats.Silky [Export(typeof(ArchiveFormat))] public class Ai6Opener : ArchiveFormat { - public override string Tag { get { return "ARC/AI6WIN"; } } + public override string Tag { get { return "ARC/AI6WIN"; } } public override string Description { get { return "AI6WIN engine resource archive"; } } - public override uint Signature { get { return 0; } } - public override bool IsHierarchic { get { return true; } } - public override bool CanWrite { get { return false; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanWrite { get { return true; } } - public Ai6Opener () + public Ai6Opener() { Extensions = new string[] { "arc" }; } - public override ArcFile TryOpen (ArcView file) + public override ArcFile TryOpen(ArcView file) { - int count = file.View.ReadInt32 (0); - if (!IsSaneCount (count)) + int count = file.View.ReadInt32(0); + if (!IsSaneCount(count)) return null; long index_offset = 4; uint index_size = (uint)(count * (0x104 + 12)); - if (index_size > file.View.Reserve (index_offset, index_size)) + if (index_size > file.View.Reserve(index_offset, index_size)) return null; var name_buffer = new byte[0x104]; var dir = new List(); for (int i = 0; i < count; ++i) { - file.View.Read (index_offset, name_buffer, 0, (uint)name_buffer.Length); - int name_length = Array.IndexOf (name_buffer, 0); + file.View.Read(index_offset, name_buffer, 0, (uint)name_buffer.Length); + int name_length = Array.IndexOf(name_buffer, 0); if (0 == name_length) return null; if (-1 == name_length) name_length = name_buffer.Length; - byte key = (byte)(name_length+1); + byte key = (byte)(name_length + 1); for (int j = 0; j < name_length; ++j) { name_buffer[j] -= key--; char c = (char)name_buffer[j]; - if (VFS.InvalidFileNameChars.Contains (c) && c != '/') + if (VFS.InvalidFileNameChars.Contains(c) && c != '/') return null; } - var name = Encodings.cp932.GetString (name_buffer, 0, name_length); + var name = Encodings.cp932.GetString(name_buffer, 0, name_length); index_offset += 0x104; - var entry = FormatCatalog.Instance.Create (name); - entry.Size = Binary.BigEndian (file.View.ReadUInt32 (index_offset)); - entry.UnpackedSize = Binary.BigEndian (file.View.ReadUInt32 (index_offset+4)); - entry.Offset = Binary.BigEndian (file.View.ReadUInt32 (index_offset+8)); - if (entry.Offset < index_size+4 || !entry.CheckPlacement (file.MaxOffset)) + var entry = FormatCatalog.Instance.Create(name); + entry.Size = Binary.BigEndian(file.View.ReadUInt32(index_offset)); + entry.UnpackedSize = Binary.BigEndian(file.View.ReadUInt32(index_offset + 4)); + entry.Offset = Binary.BigEndian(file.View.ReadUInt32(index_offset + 8)); + if (entry.Offset < index_size + 4 || !entry.CheckPlacement(file.MaxOffset)) return null; entry.IsPacked = entry.Size != entry.UnpackedSize; - dir.Add (entry); + dir.Add(entry); index_offset += 12; } - return new ArcFile (file, this, dir); + return new ArcFile(file, this, dir); } - public override Stream OpenEntry (ArcFile arc, Entry entry) + public override Stream OpenEntry(ArcFile arc, Entry entry) { var pent = entry as PackedEntry; if (null == pent || !pent.IsPacked) - return base.OpenEntry (arc, entry); - var input = arc.File.CreateStream (entry.Offset, entry.Size); - return new LzssStream (input); + return base.OpenEntry(arc, entry); + var input = arc.File.CreateStream(entry.Offset, entry.Size); + return new LzssStream(input); + } + + public override void Create(Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + int count = list.Count(); + if (0 == count) + throw new InvalidOperationException("Archive is empty"); + + uint index_size = (uint)(count * (0x104 + 12)); + uint data_offset = 4 + index_size; + + if (null != callback) + callback(count + 1, null, null); + + var entries = new List(count); + uint current_offset = data_offset; + int callback_count = 0; + + foreach (var entry in list) + { + if (null != callback) + callback(callback_count++, entry, arcStrings.MsgAddingFile); + + string name = entry.Name; + if (name.Contains("\\")) + name = name.Replace("\\", "/"); + + try + { + long fileSize = 0; + using (var input = File.OpenRead(entry.Name)) + { + fileSize = input.Length; + } + + if (fileSize > uint.MaxValue) + throw new FileLoadException("File is too large for this format."); + + var index_entry = new IndexEntry + { + SourcePath = entry.Name, + ArchiveName = name, + Size = (uint)fileSize, + UnpackedSize = (uint)fileSize, + Offset = current_offset + }; + + entries.Add(index_entry); + current_offset += (uint)fileSize; + } + catch (Exception X) + { + // 修复点:此处改用硬编码字符串或尝试 MsgFileError + throw new InvalidFileName(entry.Name, "Error opening file", X); + } + } + + if (null != callback) + callback(callback_count++, null, arcStrings.MsgWritingIndex); + + using (var writer = new BinaryWriter(output)) + { + writer.Write((uint)count); + + foreach (var entry in entries) + { + var name_bytes = Encodings.cp932.GetBytes(entry.ArchiveName); + var name_buf = new byte[0x104]; + + int copyLength = Math.Min(name_bytes.Length, name_buf.Length); + Array.Copy(name_bytes, name_buf, copyLength); + + byte key = (byte)(name_bytes.Length + 1); + + for (int i = 0; i < copyLength; ++i) + { + name_buf[i] = (byte)((name_buf[i] + key) & 0xFF); + key--; + } + + writer.Write(name_buf); + writer.Write(Binary.BigEndian(entry.Size)); + writer.Write(Binary.BigEndian(entry.UnpackedSize)); + writer.Write(Binary.BigEndian(entry.Offset)); + } + + foreach (var entry in entries) + { + using (var input = File.OpenRead(entry.SourcePath)) + { + input.CopyTo(output); + } + } + } + } + + private class IndexEntry + { + public string SourcePath { get; set; } + public string ArchiveName { get; set; } + public uint Size { get; set; } + public uint UnpackedSize { get; set; } + public uint Offset { get; set; } } } }