blizzless-diiis/src/DiIiS-NA/Core/MPQ/Data.cs
Lucca Faria Ferri f3ccab713e Game Mods - Json + Configuration migration
- Changed the Game Mods config to a json file
 - Created a .ini migration for the json file
 - Better ANSI readability
 - Improved some methods
2023-06-19 07:41:22 -07:00

384 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using CrystalMpq;
using DiIiS_NA.Core.Logging;
using DiIiS_NA.GameServer.Core.Types.SNO;
using Gibbed.IO;
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using DiIiS_NA.D3_GameServer.Core.Types.SNO;
using Spectre.Console;
namespace DiIiS_NA.Core.MPQ
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class SnoFileNameAttribute : Attribute
{
public string FileName { get; }
public SnoFileNameAttribute(string fileName)
{
FileName = fileName;
}
}
public class Data : MPQPatchChain
{
public Dictionary<SNOGroup, ConcurrentDictionary<int, Asset>> Assets = new Dictionary<SNOGroup, ConcurrentDictionary<int, Asset>>();
public readonly Dictionary<SNOGroup, Type> Parsers = new Dictionary<SNOGroup, Type>();
private readonly List<Task> _tasks = new List<Task>();
private static readonly SNOGroup[] PatchExceptions = new[] { SNOGroup.TimedEvent, SNOGroup.Script, SNOGroup.AiBehavior, SNOGroup.AiState, SNOGroup.Conductor, SNOGroup.FlagSet, SNOGroup.Code };
#region Словари
public static Dictionary<string, int> DictSNOAccolade = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOAct = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOActor = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOAdventure = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOAmbientSound = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOAnim = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOAnimation2D = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOAnimSet = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOBossEncounter = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOCondition = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOConversation = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOEffectGroup = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOEncounter = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOGameBalance = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOMarkerSet = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOMonster = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOMusic = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOObserver = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOLore = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOLevelArea = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOPower = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOPhysMesh = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNORopes = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOQuest = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOQuestRange = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNORecipe = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOScene = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOSkillKit = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOTutorial = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOWeathers = new Dictionary<string, int>();
public static Dictionary<string, int> DictSNOWorlds = new Dictionary<string, int>();
#endregion
private new static readonly Logger Logger = LogManager.CreateLogger("MPQWorker");
public Data()
//: base(0, new List<string> { "CoreData.mpq", "ClientData.mpq" }, "/base/d3-update-base-(?<version>.*?).mpq")
: base(0, new List<string> { "Core.mpq", "Core1.mpq", "Core2.mpq", "Core3.mpq", "Core4.mpq" },
"/base/d3-update-base-(?<version>.*?).mpq")
{
}
public void Init()
{
Logger.Info("Loading Diablo III Assets...");
DictSNOAccolade = Dicts.LoadAccolade();
DictSNOAct = Dicts.LoadActs();
DictSNOActor = Dicts.LoadActors();
DictSNOAdventure = Dicts.LoadAdventure();
DictSNOAmbientSound = Dicts.LoadAmbientSound();
DictSNOAnim = Dicts.LoadAnim();
DictSNOAnimation2D = Dicts.LoadAnimation2D();
DictSNOAnimSet = Dicts.LoadAnimSet();
DictSNOBossEncounter = Dicts.LoadBossEncounter();
DictSNOCondition = Dicts.LoadCondition();
DictSNOConversation = Dicts.LoadConversation();
DictSNOEffectGroup = Dicts.LoadEffectGroup();
DictSNOEncounter = Dicts.LoadEncounter();
DictSNOGameBalance = Dicts.LoadGameBalance();
DictSNOMarkerSet = Dicts.LoadMarkerSet();
DictSNOMonster = Dicts.LoadMonster();
DictSNOMusic = Dicts.LoadMusic();
DictSNOObserver = Dicts.LoadObserver();
DictSNOLore = Dicts.LoadLore();
DictSNOLevelArea = Dicts.LoadLevelArea();
DictSNOPower = Dicts.LoadPower();
DictSNOPhysMesh = Dicts.LoadPhysMesh();
DictSNOQuest = Dicts.LoadQuest();
DictSNOQuestRange = Dicts.LoadQuestRange();
DictSNORecipe = Dicts.LoadRecipe();
DictSNORopes = Dicts.LoadRopes();
DictSNOScene = Dicts.LoadScene();
DictSNOSkillKit = Dicts.LoadSkillKit();
DictSNOTutorial = Dicts.LoadTutorial();
DictSNOWeathers = Dicts.LoadWeathers();
DictSNOWorlds = Dicts.LoadWorlds();
this.InitCatalog();
this.LoadCatalogs();
}
private void InitCatalog()
{
foreach (SNOGroup group in Enum.GetValues(typeof(SNOGroup)))
{
this.Assets.Add(group, new ConcurrentDictionary<int, Asset>());
}
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
if (!type.IsSubclassOf(typeof(FileFormat))) continue;
var attributes = (FileFormatAttribute[])type.GetCustomAttributes(typeof(FileFormatAttribute), false);
if (attributes.Length == 0) continue;
Parsers.Add(attributes[0].Group, type);
}
}
private void LoadCatalogs()
{
/*
string[] MyFiles = Directory.GetFiles(@"E:\\Unpacked\\2.7.1\\Rope\\", @"*", SearchOption.AllDirectories);
string writePath = @"E:\Unpacked\Rope.txt";
int i = 0;
using (StreamWriter sw = new StreamWriter(writePath, false, System.Text.Encoding.Default))
{
foreach (var file in MyFiles)
{
var splited = file.Split('\\');
string name = splited[8].Split('.')[0];
if (name != "Axe Bad Data")
{
var asset = new MPQAsset(SNOGroup.Rope, i, name);
asset.MpqFile = this.GetFile(asset.FileName, PatchExceptions.Contains(asset.Group));
if (asset.MpqFile != null)
this.ProcessAsset(asset);
i++;
try
{
sw.WriteLine(@"('{0}', {1});", name, (asset.Data as FileFormats.Rope).Header.SNOId);
}
catch
{
Console.WriteLine("Ошибка ассета {0}", name);
sw.WriteLine(@"('{0}', {1});", name, (asset.Data as FileFormats.Rope).Header.SNOId);
}
}
}
}
//*/
this.LoadSNODict(DictSNOAccolade, SNOGroup.Accolade);
this.LoadSNODict(DictSNOAct, SNOGroup.Act);
this.LoadSNODict(DictSNOActor, SNOGroup.Actor);
this.LoadSNODict(DictSNOAdventure, SNOGroup.Adventure);
this.LoadSNODict(DictSNOAmbientSound, SNOGroup.AmbientSound);
this.LoadSNODict(DictSNOAnim, SNOGroup.Anim);
this.LoadSNODict(DictSNOAnimation2D, SNOGroup.Animation2D);
this.LoadSNODict(DictSNOAnimSet, SNOGroup.AnimSet);
this.LoadSNODict(DictSNOBossEncounter, SNOGroup.BossEncounter);
this.LoadSNODict(DictSNOConversation, SNOGroup.Conversation);
this.LoadSNODict(DictSNOEffectGroup, SNOGroup.EffectGroup);
this.LoadSNODict(DictSNOEncounter, SNOGroup.Encounter);
this.LoadSNODict(DictSNOGameBalance, SNOGroup.GameBalance);
this.LoadSNODict(DictSNOLevelArea, SNOGroup.LevelArea);
this.LoadSNODict(DictSNOLore, SNOGroup.Lore);
this.LoadSNODict(DictSNOMarkerSet, SNOGroup.MarkerSet);
this.LoadSNODict(DictSNOMonster, SNOGroup.Monster);
this.LoadSNODict(DictSNOMusic, SNOGroup.Music);
this.LoadSNODict(DictSNOObserver, SNOGroup.Observer);
this.LoadSNODict(DictSNOPhysMesh, SNOGroup.PhysMesh);
this.LoadSNODict(DictSNOPower, SNOGroup.Power);
this.LoadSNODict(DictSNOQuest, SNOGroup.Quest);
this.LoadSNODict(DictSNOQuestRange, SNOGroup.QuestRange);
this.LoadSNODict(DictSNORecipe, SNOGroup.Recipe);
this.LoadSNODict(DictSNORopes, SNOGroup.Rope);
this.LoadSNODict(DictSNOScene, SNOGroup.Scene);
this.LoadSNODict(DictSNOSkillKit, SNOGroup.SkillKit);
this.LoadSNODict(DictSNOTutorial, SNOGroup.Tutorial);
this.LoadSNODict(DictSNOWeathers, SNOGroup.Weather);
this.LoadSNODict(DictSNOWorlds, SNOGroup.Worlds);
this.LoadDBCatalog();
#if DEBUG
SnoBreakdown();
#endif
}
public void SnoBreakdown(bool fullBreakdown = false)
{
Console.WriteLine();
if (Program.IsTargetEnabled("ansi"))
Console.Clear();
var breakdownChart = new BreakdownChart()
.FullSize()
.AddItem("Actor", DictSNOActor.Count, Color.Blue)
.AddItem("Effect Group", DictSNOEffectGroup.Count, Color.Yellow)
.AddItem("Game Balance", DictSNOGameBalance.Count, Color.Cyan3)
.AddItem("Monster", DictSNOMonster.Count, Color.Red)
.AddItem("Power", DictSNOPower.Count, Color.LightPink1)
.AddItem("Quest", DictSNOQuest.Count, Color.Fuchsia)
.AddItem("Quest Range", DictSNOQuestRange.Count, Color.Magenta2_1)
.AddItem("Recipe", DictSNORecipe.Count, Color.Lime)
.AddItem("Scene", DictSNOScene.Count, Color.DarkOrange3);
if (fullBreakdown)
{
breakdownChart.AddItem("Accolade", DictSNOAccolade.Count, Color.Gold1)
.AddItem("Boss Encounter", DictSNOBossEncounter.Count, Color.Cornsilk1)
.AddItem("Act", DictSNOAct.Count, Color.IndianRed)
.AddItem("Adventure", DictSNOAdventure.Count, Color.Orange4_1)
.AddItem("Ambient Sound", DictSNOAmbientSound.Count, Color.OrangeRed1)
.AddItem("Animations", DictSNOAnim.Count, Color.Orchid)
.AddItem("Animation 2D", DictSNOAnimation2D.Count, Color.BlueViolet)
.AddItem("Animation Set", DictSNOAnimSet.Count, Color.LightGoldenrod1)
.AddItem("Conversation", DictSNOConversation.Count, Color.Aquamarine1_1)
.AddItem("Encounter", DictSNOEncounter.Count, Color.Green3_1)
.AddItem("Level Area", DictSNOLevelArea.Count, Color.Grey62)
.AddItem("Lore", DictSNOLore.Count, Color.Plum4)
.AddItem("Marker Set", DictSNOMarkerSet.Count, Color.Salmon1)
.AddItem("Music", DictSNOMusic.Count, Color.Olive)
.AddItem("Observer", DictSNOObserver.Count, Color.Violet)
.AddItem("Phys Mesh", DictSNOPhysMesh.Count, Color.CornflowerBlue)
.AddItem("Ropes", DictSNORopes.Count, Color.Yellow2)
.AddItem("Skill Kit", DictSNOSkillKit.Count, Color.DeepPink4_1)
.AddItem("Tutorial", DictSNOTutorial.Count, Color.NavajoWhite3)
.AddItem("Weather", DictSNOWeathers.Count, Color.Navy)
.AddItem("Worlds", DictSNOWorlds.Count, Color.SlateBlue3_1);
}
AnsiConsole.Write(breakdownChart);
Console.WriteLine();
}
private void LoadSNODict(Dictionary<string, int> DictSNO, SNOGroup group)
{
foreach (var point in DictSNO)
{
var asset = new MPQAsset(group, point.Value, point.Key);
asset.MpqFile = this.GetFile(asset.FileName, PatchExceptions.Contains(asset.Group));
if (asset.MpqFile != null)
this.ProcessAsset(asset);
}
//Logger.Info("Loaded assets - {0}, Category - {1}", Assets[group].Count, group);
}
private void LoadCatalog(string fileName, bool useBaseMPQ = false, List<SNOGroup> groupsToLoad = null)
{
var catalogFile = this.GetFile(fileName, useBaseMPQ);
this._tasks.Clear();
if (catalogFile == null)
{
Logger.Error("Couldn't load catalog file: {0}.", fileName);
return;
}
var stream = catalogFile.Open();
var assetsCount = stream.ReadValueS32();
var timerStart = DateTime.Now;
while (stream.Position < stream.Length)
{
stream.Position += 8;
var group = (SNOGroup)stream.ReadValueS32();
var snoId = stream.ReadValueS32();
var name = stream.ReadString(128, true);
if (groupsToLoad != null && !groupsToLoad.Contains(group))
continue;
var asset = new MPQAsset(group, snoId, name);
asset.MpqFile = this.GetFile(asset.FileName, PatchExceptions.Contains(asset.Group));
if (asset.MpqFile != null)
this.ProcessAsset(asset); // process the asset.
}
stream.Close();
if (this._tasks.Count > 0) // if we're running in tasked mode, run the parser tasks.
{
foreach (var task in this._tasks)
{
task.Start();
}
Task.WaitAll(this._tasks.ToArray()); // Wait all tasks to finish.
}
GC.Collect(); // force a garbage collection.
GC.WaitForPendingFinalizers();
var elapsedTime = DateTime.Now - timerStart;
//if (Storage.Config.Instance.LazyLoading)
Logger.Trace("Found a total of {0} assets from {1} catalog and postponed loading because lazy loading is activated.", assetsCount, fileName);
//else
// Logger.Trace("Found a total of {0} assets from {1} catalog and parsed {2} of them in {3:c}.", assetsCount, fileName, this._tasks.Count, elapsedTime);
}
/// <summary>
/// Load the table of contents from the database. the database toc contains the sno ids of all objects
/// that should / can no longer be loaded from mpq because it is zeroed out or because we need to edit
/// some of the fields
/// </summary>
private void LoadDBCatalog()
{
int assetCount = 0;
var timerStart = DateTime.Now;
using (var cmd = new SqliteCommand("SELECT * FROM TOC", Storage.DBManager.MPQMirror))
{
var itemReader = cmd.ExecuteReader();
if (itemReader.HasRows)
{
while (itemReader.Read())
{
ProcessAsset(new DBAsset(
(SNOGroup)Enum.Parse(typeof(SNOGroup), itemReader["SNOGroup"].ToString()),
Convert.ToInt32(itemReader["SNOId"]),
itemReader["Name"].ToString()));
assetCount++;
}
}
}
}
/// <summary>
/// Adds the asset to the dictionary and tries to parse it if a parser
/// is found and lazy loading is deactivated
/// </summary>
/// <param name="asset">New asset to be processed</param>
private void ProcessAsset(Asset asset)
{
this.Assets[asset.Group].TryAdd(asset.SNOId, asset);
if (!this.Parsers.ContainsKey(asset.Group)) return;
asset.Parser = this.Parsers[asset.Group];
this._tasks.Add(new Task(() => asset.RunParser()));
}
private MpqFile GetFile(string fileName, bool startSearchingFromBaseMPQ = false)
{
MpqFile file = null;
if (!startSearchingFromBaseMPQ)
file = this.FileSystem.FindFile(fileName);
else
{
foreach (MpqArchive archive in this.FileSystem.Archives.Reverse()) //search mpqs starting from base
{
file = archive.FindFile(fileName);
if (file != null)
break;
}
}
return file;
}
}
}