From 78cea042223259285caf53db686510bbc0abbf7e Mon Sep 17 00:00:00 2001 From: YeLike <93629620+YeLikesss@users.noreply.github.com> Date: Wed, 27 May 2026 01:11:31 +0800 Subject: [PATCH] =?UTF-8?q?[LightVN]=20V2=E5=B0=81=E5=8C=85=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/LightVN/ArcMCDAT.cs | 168 +++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 ArcFormats/LightVN/ArcMCDAT.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index d814fa64..3e154001 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -256,6 +256,7 @@ + diff --git a/ArcFormats/LightVN/ArcMCDAT.cs b/ArcFormats/LightVN/ArcMCDAT.cs new file mode 100644 index 00000000..c656aa1d --- /dev/null +++ b/ArcFormats/LightVN/ArcMCDAT.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Text; +using Newtonsoft.Json; + +namespace GameRes.Formats.LightVN +{ + internal class McdatArchive : ArcFile + { + private readonly Dictionary mMap; + private readonly string mRoot; + private readonly byte[] mKey; + + public McdatArchive(ArcView arc, ArchiveFormat impl, ICollection dir, + Dictionary map, string root, byte[] key) + : base(arc, impl, dir) + { + this.mMap = map; + this.mRoot = root; + this.mKey = key; + } + + public string GetFilePath(string name) + { + if (this.mMap.TryGetValue(name, out string relativePath)) + { + return Path.Combine(this.mRoot, relativePath); + } + else + { + return string.Empty; + } + } + + public void RestoreSize() + { + foreach(Entry e in this.Dir) + { + string path = this.GetFilePath(e.Name); + if(!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) + { + FileInfo fi = new FileInfo(path); + e.Size = (uint)fi.Length; + } + } + } + + public void Decrypt(byte[] data) + { + McdatArchive.Decrypt(data, this.mKey, 100); + } + + public static void Decrypt(byte[] data, byte[] key, int length) + { + int dataLen = data.Length; + + int decLen; + if (length < 0) + { + decLen = dataLen; + } + else + { + decLen = Math.Min(dataLen, length); + } + + for (int i = 1; i < decLen; ++i) + { + byte k = key[i % key.Length]; + data[dataLen - i] ^= k; + } + + for (int i = 0; i < decLen; ++i) + { + byte k = key[i % key.Length]; + data[i] ^= k; + } + } + } + + [Export(typeof(ArchiveFormat))] + public class McdatOpener : ArchiveFormat + { + public override string Tag => "MCDAT/LightVN"; + public override string Description => "LightVN Engine resource archive"; + public override uint Signature => 0u; + public override bool IsHierarchic => true; + public override bool CanWrite => false; + + public McdatOpener() + { + Extensions = new string[] { "mcdat" }; + } + + private static readonly string smIndexRelativePath = "\\Data\\_\\0.mcdat"; + private static readonly byte[] smDefaultKey = new byte[] + { + 0x64, 0x36, 0x63, 0x35, 0x66, 0x4B, 0x49, 0x33, 0x47, 0x67, 0x42, 0x57, 0x70, 0x5A, 0x46, 0x33, + 0x54, 0x7A, 0x36, 0x69, 0x61, 0x33, 0x6B, 0x46, 0x30, + }; + + + public override ArcFile TryOpen(ArcView file) + { + if (!file.Name.EndsWith(smIndexRelativePath)) + { + return null; + } + + string root = file.Name.Remove(file.Name.Length - smIndexRelativePath.Length); + byte[] key = this.QueryKey(); + + byte[] index = file.View.ReadBytes(0L, (uint)file.MaxOffset); + McdatArchive.Decrypt(index, key, -1); + + Dictionary map = null; + try + { + string json = Encoding.UTF8.GetString(index); + map = JsonConvert.DeserializeObject>(json); + } + catch + { + return null; + } + + if (map == null) + { + return null; + } + + List entries = new List(map.Count); + foreach (string name in map.Keys) + { + Entry entry = Create(name); + entry.Offset = 0L; + entry.Size = 0u; + entries.Add(entry); + } + + McdatArchive mcdatArc = new McdatArchive(file, this, entries, map, root, key); + mcdatArc.RestoreSize(); + return mcdatArc; + } + + public override Stream OpenEntry(ArcFile arc, Entry entry) + { + McdatArchive mcdatArc = (McdatArchive)arc; + + string path = mcdatArc.GetFilePath(entry.Name); + if (!string.IsNullOrEmpty(path) && File.Exists(path)) + { + byte[] data = File.ReadAllBytes(path); + mcdatArc.Decrypt(data); + return new MemoryStream(data, false); + } + return Stream.Null; + } + + private byte[] QueryKey() + { + return smDefaultKey; + } + } +}