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 1/2] =?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;
+ }
+ }
+}
From 331af3a70cdaa760b62c5c66291df3cc35372859 Mon Sep 17 00:00:00 2001
From: YeLike <93629620+YeLikesss@users.noreply.github.com>
Date: Wed, 27 May 2026 01:44:38 +0800
Subject: [PATCH 2/2] Add License
---
ArcFormats/LightVN/ArcMCDAT.cs | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/ArcFormats/LightVN/ArcMCDAT.cs b/ArcFormats/LightVN/ArcMCDAT.cs
index c656aa1d..e4b1330f 100644
--- a/ArcFormats/LightVN/ArcMCDAT.cs
+++ b/ArcFormats/LightVN/ArcMCDAT.cs
@@ -1,4 +1,29 @@
-using System;
+//! \file ArcMCDAT.cs
+//! \date 2026-5-27
+//! \brief LightVN Engine mcdat resource archive
+//
+// Copyright (C) 2026 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;