카테고리 없음

modinfo Xml에서 md5 계산해서 파일에 올리는 프로그램

hyuckkim 2022. 4. 16. 16:16

문명 5 모드는 modinfo를 루트로 가지는데 modinfo는 참조할 파일들과 함께 그 파일들의 md5 해시를 적어 둔다.
https://github.com/hyuckkim/vp_kr 이거 만들면서

GitHub - hyuckkim/vp_kr

Contribute to hyuckkim/vp_kr development by creating an account on GitHub.

github.com

해시가 틀려도 체감되는 문제는 딱히 없는것 같긴 한데 그래도 원모드에서도 해시를 열심히 챙기니까 나도 챙겨야겠다고 생각이 들었다.
모드 만드는 프로그램에서는 아마 해시를 자동으로 바꿔 줄텐데 나는 그런거 없어서 걍 C#으로 대충 만들었다.

using System.Xml;
using System.Security.Cryptography;

DirectoryInfo info = new("../");
MD5 md5 = MD5.Create();

foreach (DirectoryInfo dir in info.GetDirectories())
{
    FileInfo? modInfo = GetModInfo(dir);

    if (modInfo is not null)
    {
        modifyModinfo(modInfo, dir.FullName);
    }
}
void modifyModinfo(FileInfo modInfo, string path)
{
    XmlDocument doc = GetDocumentByPath(modInfo);
    XmlNodeList nodes = GetFileNodes(doc);

    foreach (XmlNode node in nodes)
    {
        string fileName = node.InnerText;

        XmlElement element = (XmlElement)node;
        string oldHash = element.GetAttribute("md5");
        SetAttributeByMD5(path, element);
        string newHash = element.GetAttribute("md5");

        if (oldHash != newHash)
        {
            Console.WriteLine($"{fileName}의 해시를 수정했습니다 : {oldHash.Substring(0, 8)}.. -> {newHash.Substring(0, 8)}..");
        }
    }
    using FileStream stream = modInfo.Open(FileMode.Create);
    doc.Save(stream);
}
FileInfo? GetModInfo(DirectoryInfo dir) => dir
    .GetFiles()
    .Where(e => e.Extension == ".modinfo")
    .FirstOrDefault();

XmlDocument GetDocumentByPath(FileInfo path)
{
    XmlDocument doc = new();
    FileStream stream = path.OpenRead();
    doc.Load(stream);
    stream.Close();

    return doc;
}
XmlNodeList GetFileNodes(XmlDocument doc) => doc
    .SelectNodes("Mod/Files/File") 
    ?? throw new Exception("유효한 modinfo 파일이 아닙니다.");

void SetAttributeByMD5(string path, XmlElement node)
{
    string hash = CalculateMD5ByFile($"{path}/{node.InnerText}");
    node.SetAttribute("md5", hash);
}
string CalculateMD5ByFile(string path) => md5
    .ComputeHash(File.OpenRead(path))
    .Aggregate("", (acc, x) => $"{acc}{x:x2}")
    .ToUpper();

C# 8.0 대화식이다. 대화식 나오고 진짜 많이 쓴다.

DirectoryInfo info = new("../");

현재 디렉터리의 상위 디렉터리에서 시작한다. 원래 exe 파일 하나 루트에 갖다 놓고 "." 으로 쓰려고 했는데 dll이랑 json 파일 몇 개를 더 놔야 해서 그냥 전용 폴더를 하나 만들기로 했다.


foreach (DirectoryInfo dir in info.GetDirectories())
{
    FileInfo? modInfo = GetModInfo(dir);

    if (modInfo is not null)
    {
        modifyModinfo(modInfo, dir.FullName);
    }
}

각 폴더별로 (여기서는 각 모드가 될 것이다) modinfo 파일을 찾아서 본격적인 로직을 실행한다.


FileInfo? GetModInfo(DirectoryInfo dir) => dir
    .GetFiles()
    .Where(e => e.Extension == ".modinfo")
    .FirstOrDefault();

modinfo 파일 찾는 방법.
설명할 게 없다. e가 FileInfo라는 것 정도?

    foreach (XmlNode node in nodes)
    {
        string fileName = node.InnerText;

        XmlElement element = (XmlElement)node;
        string oldHash = element.GetAttribute("md5");
        SetAttributeByMD5(path, element);
        string newHash = element.GetAttribute("md5");

        if (oldHash != newHash)
        {
            Console.WriteLine($"{fileName}의 해시를 수정했습니다 : {oldHash.Substring(0, 8)}.. -> {newHash.Substring(0, 8)}..");
        }
    }

이 부분은 지금 보니까 살짝 맘에 안 든다. 부분을 change / display로 바꾸고 둘다 각 함수로 분리하고 싶다.

string CalculateMD5ByFile(string path) => md5
    .ComputeHash(File.OpenRead(path))
    .Aggregate("", (acc, x) => $"{acc}{x:x2}")
    .ToUpper();

MD5 생성하기. Aggregate가 다른 언어의 Reduce다. ""로 시작해서 각 글자 붙이기 반복.
지금 생각해보니까 stringbuilder().tostring 하는 게 좀 더 빨랐을 거 같긴 한데 바로 위에 File.OpenRead()가 있어서 티도 안 났을 것 같다.

Console.WriteLine($
"{fileName}의 해시를 수정했습니다 : 
{oldHash.Substring(0, 8)}.. 
-> 
{newHash.Substring(0, 8)}..");

안내 메시지. 파일이 수정되면 파일 이름과 전후 해시를 보여준다.

실제로 해시를 출력한 결과.

C#으로 뭔가 만들 때마다 굉장히 기분 좋게 만들어진다. Linq는 신이고 코드는 읽으면 이해된다. 앞으로도 C#을 많이 쓰고 싶다. 제발~~~~