2023-11-12 01:59:17 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using CodeWalker.Core.Utils;
|
2024-01-07 02:41:10 +08:00
|
|
|
|
using CodeWalker.World;
|
|
|
|
|
using CommunityToolkit.HighPerformance;
|
2023-10-28 03:31:09 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
using System.Collections.Generic;
|
2023-10-28 03:31:09 +08:00
|
|
|
|
using System.Diagnostics;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
2024-01-07 02:41:10 +08:00
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
using System.Runtime.InteropServices;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
using System.Text;
|
2023-11-14 23:16:59 +08:00
|
|
|
|
using System.Threading;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using System.Xml;
|
2023-11-14 23:16:59 +08:00
|
|
|
|
using System.Xml.Linq;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
|
|
|
|
namespace CodeWalker.GameFiles
|
|
|
|
|
{
|
|
|
|
|
public class RpfManager
|
|
|
|
|
{
|
2023-10-28 03:31:09 +08:00
|
|
|
|
private static RpfManager _instance = new RpfManager();
|
|
|
|
|
public static RpfManager GetInstance()
|
|
|
|
|
{
|
|
|
|
|
return _instance ??= new RpfManager();
|
|
|
|
|
}
|
2017-09-21 18:33:05 +08:00
|
|
|
|
//for caching and management of RPF file data.
|
|
|
|
|
|
|
|
|
|
public string Folder { get; private set; }
|
|
|
|
|
public string[] ExcludePaths { get; set; }
|
|
|
|
|
public bool EnableMods { get; set; }
|
2019-01-11 11:24:50 +08:00
|
|
|
|
public bool BuildExtendedJenkIndex { get; set; } = true;
|
2023-10-28 03:31:09 +08:00
|
|
|
|
public event Action<string> UpdateStatus;
|
|
|
|
|
public event Action<string> ErrorLog;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
|
|
|
|
public List<RpfFile> BaseRpfs { get; private set; }
|
|
|
|
|
public List<RpfFile> ModRpfs { get; private set; }
|
|
|
|
|
public List<RpfFile> DlcRpfs { get; private set; }
|
|
|
|
|
public List<RpfFile> AllRpfs { get; private set; }
|
|
|
|
|
public List<RpfFile> DlcNoModRpfs { get; private set; }
|
|
|
|
|
public List<RpfFile> AllNoModRpfs { get; private set; }
|
|
|
|
|
public Dictionary<string, RpfFile> RpfDict { get; private set; }
|
|
|
|
|
public Dictionary<string, RpfEntry> EntryDict { get; private set; }
|
|
|
|
|
public Dictionary<string, RpfFile> ModRpfDict { get; private set; }
|
|
|
|
|
public Dictionary<string, RpfEntry> ModEntryDict { get; private set; }
|
|
|
|
|
|
|
|
|
|
public volatile bool IsInited = false;
|
|
|
|
|
|
2023-11-14 23:16:59 +08:00
|
|
|
|
private const int DefaultEntryDictCapacity = 354878;
|
|
|
|
|
private const int DefaultRpfDictCapacity = 4650;
|
|
|
|
|
|
2017-12-24 19:49:04 +08:00
|
|
|
|
public void Init(string folder, Action<string> updateStatus, Action<string> errorLog, bool rootOnly = false, bool buildIndex = true)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
using var timer = new DisposableTimer("RpfManager.Init");
|
2023-10-28 03:31:09 +08:00
|
|
|
|
UpdateStatus += updateStatus;
|
|
|
|
|
ErrorLog += errorLog;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
|
|
|
|
string replpath = folder + "\\";
|
|
|
|
|
var sopt = rootOnly ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories;
|
|
|
|
|
string[] allfiles = Directory.GetFiles(folder, "*.rpf", sopt);
|
|
|
|
|
|
2023-11-14 23:16:59 +08:00
|
|
|
|
BaseRpfs = new List<RpfFile>(1300);
|
|
|
|
|
ModRpfs = new List<RpfFile>(0);
|
|
|
|
|
DlcRpfs = new List<RpfFile>(3500);
|
2024-01-08 12:00:55 +08:00
|
|
|
|
AllRpfs ??= new List<RpfFile>(0);
|
2023-11-14 23:16:59 +08:00
|
|
|
|
DlcNoModRpfs = new List<RpfFile>(3500);
|
|
|
|
|
AllNoModRpfs = new List<RpfFile>(5000);
|
2024-01-08 12:00:55 +08:00
|
|
|
|
RpfDict = new Dictionary<string, RpfFile>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
EntryDict = new Dictionary<string, RpfEntry>(StringComparer.OrdinalIgnoreCase);
|
2023-11-12 01:59:17 +08:00
|
|
|
|
ModRpfDict = new Dictionary<string, RpfFile>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
ModEntryDict = new Dictionary<string, RpfEntry>(StringComparer.OrdinalIgnoreCase);
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
2024-01-08 12:00:55 +08:00
|
|
|
|
FileCounts _fileCounts = default;
|
|
|
|
|
|
2023-10-28 03:31:09 +08:00
|
|
|
|
var rpfs = new ConcurrentBag<RpfFile>();
|
|
|
|
|
Parallel.ForEach(allfiles, (rpfpath) =>
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
RpfFile rf = new RpfFile(rpfpath, rpfpath.Replace(replpath, ""));
|
|
|
|
|
|
2024-01-08 12:00:55 +08:00
|
|
|
|
if (ExcludePaths is not null)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
bool excl = false;
|
2024-01-08 12:00:55 +08:00
|
|
|
|
foreach(var path in ExcludePaths)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2024-01-08 12:00:55 +08:00
|
|
|
|
if (rf.Path.StartsWith(path, StringComparison.OrdinalIgnoreCase))
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
excl = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-08 12:00:55 +08:00
|
|
|
|
if (excl)
|
|
|
|
|
return; //skip files in exclude paths.
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-01-08 12:00:55 +08:00
|
|
|
|
rf.ScanStructure(updateStatus, errorLog, out var fileCounts);
|
|
|
|
|
|
|
|
|
|
lock(this)
|
|
|
|
|
{
|
|
|
|
|
_fileCounts += fileCounts;
|
|
|
|
|
}
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
2019-03-20 18:21:47 +08:00
|
|
|
|
if (rf.LastException != null) //incase of corrupted rpf (or renamed NG encrypted RPF)
|
|
|
|
|
{
|
2023-11-14 23:16:59 +08:00
|
|
|
|
Console.WriteLine(rf.LastException);
|
2023-10-28 03:31:09 +08:00
|
|
|
|
return;
|
2019-03-20 18:21:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-10-28 03:31:09 +08:00
|
|
|
|
rpfs.Add(rf);
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
Console.WriteLine(ex);
|
|
|
|
|
errorLog?.Invoke($"{rpfpath}: {ex}");
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
2023-10-28 03:31:09 +08:00
|
|
|
|
});
|
|
|
|
|
|
2023-11-14 23:16:59 +08:00
|
|
|
|
static int calculateSum(RpfFile rpf)
|
2023-10-28 03:31:09 +08:00
|
|
|
|
{
|
|
|
|
|
return rpf.AllEntries?.Count ?? 0 + rpf.Children?.Sum(calculateSum) ?? 0;
|
2023-11-14 23:16:59 +08:00
|
|
|
|
}
|
2023-10-28 03:31:09 +08:00
|
|
|
|
|
2024-01-08 12:00:55 +08:00
|
|
|
|
AllRpfs.EnsureCapacity((int)_fileCounts.Rpfs);
|
|
|
|
|
RpfDict.EnsureCapacity((int)_fileCounts.Rpfs);
|
|
|
|
|
AllNoModRpfs.EnsureCapacity((int)_fileCounts.Rpfs);
|
|
|
|
|
EntryDict.EnsureCapacity((int)_fileCounts.Rpfs + (int)_fileCounts.Files);
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
2024-01-08 12:00:55 +08:00
|
|
|
|
foreach (var rpf in rpfs)
|
2017-12-24 19:49:04 +08:00
|
|
|
|
{
|
2023-10-28 03:31:09 +08:00
|
|
|
|
AddRpfFile(rpf, false, false);
|
2017-12-24 19:49:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-10-28 03:31:09 +08:00
|
|
|
|
if (buildIndex)
|
|
|
|
|
{
|
|
|
|
|
Task.Run(() =>
|
|
|
|
|
{
|
2023-11-14 23:16:59 +08:00
|
|
|
|
updateStatus?.Invoke("Building jenkindex...");
|
2023-10-28 03:31:09 +08:00
|
|
|
|
BuildBaseJenkIndex();
|
|
|
|
|
IsInited = true;
|
2023-11-14 23:16:59 +08:00
|
|
|
|
updateStatus?.Invoke("Scan complete");
|
2023-10-28 03:31:09 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
updateStatus?.Invoke("Scan complete");
|
|
|
|
|
IsInited = true;
|
|
|
|
|
}
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
2024-01-08 12:00:55 +08:00
|
|
|
|
Console.WriteLine($"fileCounts: {_fileCounts}");
|
2023-11-14 23:16:59 +08:00
|
|
|
|
Console.WriteLine($"AllRpfs: {AllRpfs.Count}; RpfDict: {RpfDict.Count}; EntryDict: {EntryDict.Count}; BaseRpfs: {BaseRpfs.Count}; ModRpfs: {ModRpfs.Count}; DlcRpfs: {DlcRpfs.Count}; DlcNoModRpfs: {DlcNoModRpfs.Count}; AllNoModRpfs: {AllNoModRpfs.Count}; ModRpfDict: {ModRpfDict.Count}; ModEntryDict: {ModEntryDict.Count}");
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Init(List<RpfFile> allRpfs)
|
|
|
|
|
{
|
2023-10-28 03:31:09 +08:00
|
|
|
|
using var _ = new DisposableTimer("RpfManager.Init");
|
2017-09-21 18:33:05 +08:00
|
|
|
|
//fast init used by RPF explorer's File cache
|
|
|
|
|
AllRpfs = allRpfs;
|
|
|
|
|
|
|
|
|
|
BaseRpfs = new List<RpfFile>();
|
|
|
|
|
ModRpfs = new List<RpfFile>();
|
|
|
|
|
DlcRpfs = new List<RpfFile>();
|
|
|
|
|
DlcNoModRpfs = new List<RpfFile>();
|
|
|
|
|
AllNoModRpfs = new List<RpfFile>();
|
2023-11-14 23:16:59 +08:00
|
|
|
|
RpfDict = new Dictionary<string, RpfFile>(DefaultRpfDictCapacity, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
EntryDict = new Dictionary<string, RpfEntry>(DefaultEntryDictCapacity, StringComparer.OrdinalIgnoreCase);
|
2023-11-12 01:59:17 +08:00
|
|
|
|
ModRpfDict = new Dictionary<string, RpfFile>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
ModEntryDict = new Dictionary<string, RpfEntry>(StringComparer.OrdinalIgnoreCase);
|
2017-09-21 18:33:05 +08:00
|
|
|
|
foreach (var rpf in allRpfs)
|
|
|
|
|
{
|
|
|
|
|
RpfDict[rpf.Path] = rpf;
|
2023-11-14 23:16:59 +08:00
|
|
|
|
if (rpf.AllEntries == null)
|
|
|
|
|
continue;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
foreach (var entry in rpf.AllEntries)
|
|
|
|
|
{
|
|
|
|
|
EntryDict[entry.Path] = entry;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-14 23:16:59 +08:00
|
|
|
|
Console.WriteLine($"RpfDict: {RpfDict.Count}; EntryDict: {EntryDict.Count}");
|
|
|
|
|
|
2023-10-28 03:31:09 +08:00
|
|
|
|
Task.Run(() =>
|
|
|
|
|
{
|
|
|
|
|
BuildBaseJenkIndex();
|
|
|
|
|
IsInited = true;
|
|
|
|
|
});
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void AddRpfFile(RpfFile file, bool isdlc, bool ismod)
|
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
if (file.AllEntries is null && file.Children is null)
|
2023-11-14 23:16:59 +08:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
isdlc = isdlc || (file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase) && (file.Name.StartsWith("dlc", StringComparison.OrdinalIgnoreCase) || file.Name.Equals("update.rpf", StringComparison.OrdinalIgnoreCase)));
|
2023-11-12 01:59:17 +08:00
|
|
|
|
ismod = ismod || (file.Path.StartsWith("mods\\", StringComparison.OrdinalIgnoreCase));
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
2024-01-08 12:00:55 +08:00
|
|
|
|
if (file.AllEntries is not null)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
AllRpfs.Add(file);
|
|
|
|
|
if (!ismod)
|
|
|
|
|
{
|
|
|
|
|
AllNoModRpfs.Add(file);
|
|
|
|
|
}
|
|
|
|
|
if (isdlc)
|
|
|
|
|
{
|
|
|
|
|
DlcRpfs.Add(file);
|
|
|
|
|
if (!ismod)
|
|
|
|
|
{
|
|
|
|
|
DlcNoModRpfs.Add(file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (ismod)
|
|
|
|
|
{
|
|
|
|
|
ModRpfs.Add(file);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
BaseRpfs.Add(file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (ismod)
|
|
|
|
|
{
|
|
|
|
|
ModRpfDict[file.Path.Substring(5)] = file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RpfDict[file.Path] = file;
|
|
|
|
|
|
|
|
|
|
foreach (RpfEntry entry in file.AllEntries)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrEmpty(entry.Name))
|
|
|
|
|
{
|
|
|
|
|
if (ismod)
|
|
|
|
|
{
|
|
|
|
|
ModEntryDict[entry.Path] = entry;
|
|
|
|
|
ModEntryDict[entry.Path.Substring(5)] = entry;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-01-08 12:00:55 +08:00
|
|
|
|
_ = EntryDict.TryAdd(entry.Path, entry);
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
file.LastError = ex.ToString();
|
|
|
|
|
file.LastException = ex;
|
2024-01-08 12:00:55 +08:00
|
|
|
|
ErrorLog?.Invoke($"{entry.Path}: {ex}");
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-07 02:41:10 +08:00
|
|
|
|
if (file.Children is not null)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
foreach (RpfFile cfile in file.Children)
|
|
|
|
|
{
|
|
|
|
|
AddRpfFile(cfile, isdlc, ismod);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-01-07 02:41:10 +08:00
|
|
|
|
public RpfFile? FindRpfFile(string path) => FindRpfFile(path, false);
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
2019-06-03 15:12:52 +08:00
|
|
|
|
|
2024-01-07 02:41:10 +08:00
|
|
|
|
public RpfFile? FindRpfFile(string path, bool exactPathOnly)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
if (EnableMods && ModRpfDict.TryGetValue(path, out RpfFile? file))
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
return file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (RpfDict.TryGetValue(path, out file))
|
|
|
|
|
{
|
|
|
|
|
return file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (RpfFile tfile in AllRpfs)
|
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
if (!exactPathOnly && tfile.Name.Equals(path, StringComparison.OrdinalIgnoreCase))
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
return tfile;
|
|
|
|
|
}
|
2023-11-12 01:59:17 +08:00
|
|
|
|
if (tfile.Path.Equals(path, StringComparison.OrdinalIgnoreCase))
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
return tfile;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-01-07 02:41:10 +08:00
|
|
|
|
public RpfEntry? GetEntry(string path)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
if (EnableMods && ModEntryDict.TryGetValue(path, out RpfEntry? entry))
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
2023-11-12 01:59:17 +08:00
|
|
|
|
EntryDict.TryGetValue(path, out entry);
|
2024-01-07 02:41:10 +08:00
|
|
|
|
if (entry is null)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
path = path.Replace("/", "\\");
|
|
|
|
|
path = path.Replace("common:", "common.rpf");
|
|
|
|
|
if (EnableMods && ModEntryDict.TryGetValue(path, out entry))
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
2023-11-12 01:59:17 +08:00
|
|
|
|
EntryDict.TryGetValue(path, out entry);
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
public byte[]? GetFileData(string path)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
byte[]? data = null;
|
|
|
|
|
if (GetEntry(path) is RpfFileEntry entry)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
data = entry.File.ExtractFile(entry);
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
public string GetFileUTF8Text(string path)
|
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
byte[]? bytes = GetFileData(path);
|
|
|
|
|
var text = TextUtil.GetUTF8Text(bytes);
|
|
|
|
|
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public StreamReader GetFileUTF8TextStream(string path)
|
|
|
|
|
{
|
|
|
|
|
byte[]? bytes = GetFileData(path);
|
|
|
|
|
|
|
|
|
|
return new StreamReader(bytes.AsMemory().AsStream(), new UTF8Encoding(false), true); ;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
2023-11-14 23:16:59 +08:00
|
|
|
|
|
2017-09-21 18:33:05 +08:00
|
|
|
|
public XmlDocument GetFileXml(string path)
|
|
|
|
|
{
|
|
|
|
|
XmlDocument doc = new XmlDocument();
|
|
|
|
|
string text = GetFileUTF8Text(path);
|
|
|
|
|
if (!string.IsNullOrEmpty(text))
|
|
|
|
|
{
|
|
|
|
|
doc.LoadXml(text);
|
|
|
|
|
}
|
2023-11-14 23:16:59 +08:00
|
|
|
|
|
2017-09-21 18:33:05 +08:00
|
|
|
|
return doc;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-07 02:41:10 +08:00
|
|
|
|
public XmlReader GetFileXmlReader(string path, XmlNameTable nameTable)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
var text = GetFileUTF8TextStream(path);
|
|
|
|
|
|
|
|
|
|
var reader = XmlReader.Create(text, new XmlReaderSettings { NameTable = nameTable });
|
|
|
|
|
|
|
|
|
|
return reader;
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
|
|
|
|
|
public XmlReader GetFileXmlReader(string path)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
var text = GetFileUTF8TextStream(path);
|
|
|
|
|
|
|
|
|
|
var reader = XmlReader.Create(text);
|
|
|
|
|
|
|
|
|
|
return reader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public T? GetFile<T>(string path) where T : class, PackedFile, new()
|
|
|
|
|
{
|
|
|
|
|
if (GetEntry(path) is not RpfFileEntry entry)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return GetFile<T>(entry);
|
|
|
|
|
}
|
|
|
|
|
public static T? GetFile<T>(RpfEntry e) where T : class, PackedFile, new()
|
|
|
|
|
{
|
|
|
|
|
if (e is not RpfFileEntry entry)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
byte[]? data = entry.File.ExtractFile(entry);
|
|
|
|
|
T? file = null;
|
|
|
|
|
if (data is not null)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
file = new T();
|
|
|
|
|
file.Load(data, entry);
|
|
|
|
|
}
|
|
|
|
|
return file;
|
|
|
|
|
}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
|
|
|
|
|
public ValueTask<T?> GetFileAsync<T>(string path) where T : class, PackedFile, new()
|
|
|
|
|
{
|
|
|
|
|
RpfFileEntry? entry = GetEntry(path) as RpfFileEntry;
|
|
|
|
|
|
|
|
|
|
if (entry is null)
|
|
|
|
|
return ValueTask.FromResult((T?)null);
|
|
|
|
|
|
|
|
|
|
return GetFileAsync<T>(entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static async ValueTask<T?> GetFileAsync<T>(RpfEntry e) where T : class, PackedFile, new()
|
2023-11-12 01:59:17 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
if (e is not RpfFileEntry entry)
|
|
|
|
|
return null;
|
|
|
|
|
|
2023-11-12 01:59:17 +08:00
|
|
|
|
try
|
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
byte[]? data = await entry.File.ExtractFileAsync(entry);
|
|
|
|
|
T? file = null;
|
|
|
|
|
if (data is not null && data.Length > 0)
|
2023-11-12 01:59:17 +08:00
|
|
|
|
{
|
|
|
|
|
file = new T();
|
|
|
|
|
file.Load(data, entry);
|
|
|
|
|
}
|
|
|
|
|
return file;
|
2023-11-14 23:16:59 +08:00
|
|
|
|
}
|
|
|
|
|
catch(Exception ex)
|
2023-11-12 01:59:17 +08:00
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(ex);
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-21 18:33:05 +08:00
|
|
|
|
public bool LoadFile<T>(T file, RpfEntry e) where T : class, PackedFile
|
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
byte[]? data = null;
|
|
|
|
|
RpfFileEntry? entry = e as RpfFileEntry;
|
|
|
|
|
if (entry is not null)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
data = entry.File.ExtractFile(entry);
|
|
|
|
|
}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
|
|
|
|
|
if (data is not null && data.Length > 0)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
file.Load(data, entry);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch(Exception ex)
|
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
Console.WriteLine($"Error occured while loading {e.Name} at {e.Path}:\n{ex}");
|
2023-11-12 01:59:17 +08:00
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async ValueTask<bool> LoadFileAsync<T>(T file, RpfEntry e) where T : class, PackedFile
|
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
byte[]? data = null;
|
|
|
|
|
RpfFileEntry? entry = e as RpfFileEntry;
|
|
|
|
|
if (entry is not null)
|
2023-11-12 01:59:17 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
data = await entry.File.ExtractFileAsync(entry);
|
2023-11-12 01:59:17 +08:00
|
|
|
|
}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
if (data is not null && data.Length > 0)
|
2023-11-12 01:59:17 +08:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
file.Load(data, entry);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch(Exception ex)
|
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
Console.WriteLine($"Error occured while loading {e.Name} at {e.Path}:\n{ex}");
|
2023-11-12 01:59:17 +08:00
|
|
|
|
throw;
|
|
|
|
|
}
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-14 23:16:59 +08:00
|
|
|
|
private ConcurrentDictionary<string, int> counts = new ConcurrentDictionary<string, int>();
|
2024-01-07 02:41:10 +08:00
|
|
|
|
private string[] stringLookup;
|
|
|
|
|
|
|
|
|
|
[SkipLocalsInit]
|
2023-11-14 23:16:59 +08:00
|
|
|
|
public void AddAllLods(string name)
|
|
|
|
|
{
|
|
|
|
|
var idx = name.LastIndexOf('_');
|
|
|
|
|
if (idx > 0)
|
|
|
|
|
{
|
|
|
|
|
var str1 = name.AsSpan(0, idx);
|
|
|
|
|
var idx2 = str1.LastIndexOf('_');
|
|
|
|
|
if (idx2 > 0)
|
|
|
|
|
{
|
|
|
|
|
// Filter some peds and clothing models (ydd's) which don't have LOD hashes we're interested in.
|
|
|
|
|
// This saves about 50% of the time it takes to do initial hashing
|
2024-01-07 02:41:10 +08:00
|
|
|
|
var str2 = str1.Slice(0, idx2 + 1).ToString();
|
2023-11-14 23:16:59 +08:00
|
|
|
|
if (str2.Length <= 2)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
var ignore = str2 switch
|
2023-11-14 23:16:59 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
"p_" => true,
|
|
|
|
|
"s_m_y_" => true,
|
|
|
|
|
"s_m_" => true,
|
|
|
|
|
"s_m_m_" => true,
|
|
|
|
|
"s_f_y_" => true,
|
|
|
|
|
"a_m_y_" => true,
|
|
|
|
|
"ig_" => true,
|
|
|
|
|
"u_m_y_" => true,
|
|
|
|
|
"u_m_m_" => true,
|
|
|
|
|
"u_m_" => true,
|
|
|
|
|
"minimap_" => true,
|
|
|
|
|
"a_" => true,
|
|
|
|
|
"u_f_" => true,
|
|
|
|
|
"csb_" => true,
|
|
|
|
|
"g_m_y_" => true,
|
|
|
|
|
"a_f_m_" => true,
|
|
|
|
|
"a_m_m_" => true,
|
|
|
|
|
"g_m_m_" => true,
|
|
|
|
|
"mp_m_" => true,
|
|
|
|
|
"mp_f_" => true,
|
|
|
|
|
"mp_" => true,
|
|
|
|
|
"a_f_y_" => true,
|
|
|
|
|
var str when str.StartsWith("accs_") => true,
|
|
|
|
|
var str when str.StartsWith("decl_") => true,
|
|
|
|
|
var str when str.StartsWith("berd_") => true,
|
|
|
|
|
var str when str.StartsWith("hair_") => true,
|
|
|
|
|
var str when str.StartsWith("teef_") => true,
|
|
|
|
|
var str when str.StartsWith("lowr_") => true,
|
|
|
|
|
var str when str.StartsWith("jbib_") => true,
|
|
|
|
|
var str when str.StartsWith("hand_") => true,
|
|
|
|
|
var str when str.StartsWith("feet_") => true,
|
|
|
|
|
var str when str.StartsWith("task_") => true,
|
|
|
|
|
var str when str.StartsWith("head_") => true,
|
|
|
|
|
var str when str.StartsWith("uppr_") => true,
|
|
|
|
|
_ => false,
|
|
|
|
|
};
|
|
|
|
|
if (ignore)
|
|
|
|
|
return;
|
|
|
|
|
|
2023-11-14 23:16:59 +08:00
|
|
|
|
Span<char> buff = stackalloc char[str2.Length + 2 + 4];
|
|
|
|
|
str2.CopyTo(buff.Slice(0, str2.Length));
|
2024-01-07 02:41:10 +08:00
|
|
|
|
stackalloc char[] { 'l', 'o', 'd' }.CopyTo(buff.Slice(str2.Length, 3));
|
2023-11-14 23:16:59 +08:00
|
|
|
|
//Console.WriteLine(buff.Slice(0, str2.Length + 3).ToString());
|
|
|
|
|
JenkIndex.EnsureLower(buff.Slice(0, str2.Length + 3));
|
2024-01-07 02:41:10 +08:00
|
|
|
|
const int maxi = 99;
|
2023-11-14 23:16:59 +08:00
|
|
|
|
|
2024-01-07 02:41:10 +08:00
|
|
|
|
stackalloc char[] { '0', '0', '_', 'l', 'o', 'd' }.CopyTo(buff.Slice(str2.Length, 6));
|
|
|
|
|
|
|
|
|
|
if (stringLookup is null)
|
2023-11-14 23:16:59 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
stringLookup = new string[maxi + 1];
|
|
|
|
|
for (int i = 0; i <= maxi; i++)
|
2023-11-14 23:16:59 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
stringLookup[i] = i.ToString().PadLeft(2, '0');
|
2023-11-14 23:16:59 +08:00
|
|
|
|
}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i <= maxi; i++)
|
|
|
|
|
{
|
|
|
|
|
stringLookup[i].AsSpan().CopyTo(buff.Slice(str2.Length, 2));
|
|
|
|
|
|
|
|
|
|
var hash = JenkHash.GenHashLower(buff);
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
2023-11-14 23:16:59 +08:00
|
|
|
|
//Console.WriteLine(buff.ToString());
|
|
|
|
|
//JenkIndex.Ensure(buff);
|
2024-01-07 02:41:10 +08:00
|
|
|
|
JenkIndex.Ensure(str2, hash);
|
2023-11-14 23:16:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-21 18:33:05 +08:00
|
|
|
|
|
2024-01-07 02:41:10 +08:00
|
|
|
|
[SkipLocalsInit]
|
|
|
|
|
private void parseAwc(string path)
|
|
|
|
|
{
|
|
|
|
|
var enumerator = path.ReverseEnumerateSplit('\\');
|
|
|
|
|
if (enumerator.MoveNext())
|
|
|
|
|
{
|
|
|
|
|
ReadOnlySpan<char> fn = enumerator.Current;
|
|
|
|
|
if (enumerator.MoveNext())
|
|
|
|
|
{
|
|
|
|
|
ReadOnlySpan<char> fd = enumerator.Current;
|
|
|
|
|
|
|
|
|
|
fn = fn.Slice(0, fn.Length - 4);
|
|
|
|
|
|
|
|
|
|
if (fd.EndsWith(['.', 'r', 'p', 'f'], StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
fd = fd.Slice(0, fd.Length - 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Span<char> hpath = stackalloc char[fd.Length + fn.Length + 1];
|
|
|
|
|
|
|
|
|
|
fd.CopyTo(hpath);
|
|
|
|
|
hpath[fd.Length] = '/';
|
|
|
|
|
fn.CopyTo(hpath.Slice(fd.Length + 1));
|
|
|
|
|
JenkIndex.EnsureLower(hpath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-21 18:33:05 +08:00
|
|
|
|
public void BuildBaseJenkIndex()
|
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
var yddFiles = new ConcurrentBag<string>();
|
|
|
|
|
using var timer = new DisposableTimer("BuildBaseJenkIndex");
|
|
|
|
|
Parallel.ForEach(AllRpfs, new ParallelOptions { MaxDegreeOfParallelism = 4 }, [SkipLocalsInit] (file) =>
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
JenkIndex.Ensure(file.Name);
|
|
|
|
|
foreach (RpfEntry entry in file.AllEntries)
|
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
var name = entry.Name;
|
|
|
|
|
if (string.IsNullOrEmpty(name))
|
|
|
|
|
continue;
|
2017-12-18 03:35:08 +08:00
|
|
|
|
//JenkIndex.Ensure(entry.Name);
|
|
|
|
|
//JenkIndex.Ensure(nlow);
|
2023-11-12 01:59:17 +08:00
|
|
|
|
var nameWithoutExtension = entry.ShortName;
|
|
|
|
|
JenkIndex.EnsureBoth(nameWithoutExtension);
|
|
|
|
|
|
|
|
|
|
//if (ind < entry.Name.Length - 2)
|
|
|
|
|
//{
|
|
|
|
|
// JenkIndex.Ensure(entry.Name.Substring(0, ind) + ".#" + entry.Name.Substring(ind + 2));
|
|
|
|
|
// JenkIndex.Ensure(entry.NameLower.Substring(0, ind) + ".#" + entry.NameLower.Substring(ind + 2));
|
|
|
|
|
//}
|
2019-01-11 11:24:50 +08:00
|
|
|
|
if (BuildExtendedJenkIndex)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
if (name.EndsWith(".ydr", StringComparison.OrdinalIgnoreCase))// || nlow.EndsWith(".yft")) //do yft's get lods?
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
var sname = entry.ShortName;
|
2024-01-07 02:41:10 +08:00
|
|
|
|
var nameLod = $"{sname}_lod";
|
2023-11-12 01:59:17 +08:00
|
|
|
|
JenkIndex.EnsureLower(nameLod);
|
2024-01-07 02:41:10 +08:00
|
|
|
|
JenkIndex.EnsureLower($"{nameLod}a");
|
|
|
|
|
JenkIndex.EnsureLower($"{nameLod}b");
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
2023-11-12 01:59:17 +08:00
|
|
|
|
else if (name.EndsWith(".ydd", StringComparison.OrdinalIgnoreCase))
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
if (name.EndsWith("_children.ydd", StringComparison.OrdinalIgnoreCase))
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
var strn = entry.Name.Substring(0, name.Length - 13);
|
|
|
|
|
JenkIndex.EnsureLower(strn);
|
2024-01-07 02:41:10 +08:00
|
|
|
|
var nameChildrenLod = $"{strn}_lod";
|
2023-11-12 01:59:17 +08:00
|
|
|
|
JenkIndex.EnsureLower(nameChildrenLod);
|
2024-01-07 02:41:10 +08:00
|
|
|
|
JenkIndex.EnsureLower($"{nameChildrenLod}a");
|
|
|
|
|
JenkIndex.EnsureLower($"{nameChildrenLod}b");
|
2019-01-11 11:24:50 +08:00
|
|
|
|
}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
|
|
|
|
|
yddFiles.Add(name);
|
2023-11-14 23:16:59 +08:00
|
|
|
|
//var idx = name.LastIndexOf('_');
|
|
|
|
|
//if (idx > 0)
|
|
|
|
|
//{
|
|
|
|
|
// var str1 = name.Substring(0, idx);
|
|
|
|
|
// var idx2 = str1.LastIndexOf('_');
|
|
|
|
|
// if (idx2 > 0)
|
|
|
|
|
// {
|
|
|
|
|
// var str2 = str1.Substring(0, idx2);
|
|
|
|
|
// JenkIndex.EnsureLower(str2 + "_lod");
|
|
|
|
|
// var maxi = 100;
|
|
|
|
|
|
|
|
|
|
// for (int i = 1; i <= maxi; i++)
|
|
|
|
|
// {
|
|
|
|
|
// var str3 = str2 + '_' + i.ToString().PadLeft(2, '0') + "_lod";
|
|
|
|
|
// //JenkIndex.Ensure(str3);
|
|
|
|
|
// JenkIndex.EnsureLower(str3);
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
//AddAllLods(name);
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
2023-11-12 01:59:17 +08:00
|
|
|
|
else if(name.EndsWith(".sps", StringComparison.OrdinalIgnoreCase))
|
2019-03-21 22:29:37 +08:00
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
JenkIndex.EnsureLower(entry.Name);//for shader preset filename hashes!
|
2019-03-21 22:29:37 +08:00
|
|
|
|
}
|
2023-11-12 01:59:17 +08:00
|
|
|
|
else if(name.EndsWith(".awc", StringComparison.OrdinalIgnoreCase)) //create audio container path hashes...
|
2017-12-23 04:56:39 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
parseAwc(entry.Path);
|
2017-12-23 04:56:39 +08:00
|
|
|
|
}
|
2023-11-12 01:59:17 +08:00
|
|
|
|
else if(name.EndsWith(".nametable", StringComparison.OrdinalIgnoreCase))
|
2017-12-23 04:56:39 +08:00
|
|
|
|
{
|
2023-11-12 01:59:17 +08:00
|
|
|
|
if (entry is RpfBinaryFileEntry binfe)
|
2017-12-23 04:56:39 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
byte[] rawData = file.ExtractFile(binfe);
|
|
|
|
|
if (rawData != null)
|
2017-12-23 04:56:39 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
foreach(var bytes in rawData.AsSpan().EnumerateSplit((byte)0))
|
2017-12-23 04:56:39 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
string str = Encoding.ASCII.GetString(bytes);
|
|
|
|
|
if (!string.IsNullOrEmpty(str))
|
2019-01-11 11:24:50 +08:00
|
|
|
|
{
|
2024-01-07 02:41:10 +08:00
|
|
|
|
//JenkIndex.Ensure(str);
|
|
|
|
|
|
|
|
|
|
JenkIndex.EnsureLower(str);
|
|
|
|
|
|
|
|
|
|
////DirMod_Sounds_ entries apparently can be used to infer SP audio strings
|
|
|
|
|
////no luck here yet though
|
|
|
|
|
//if (strl.StartsWith("dirmod_sounds_") && (strl.Length > 14))
|
|
|
|
|
//{
|
|
|
|
|
// strl = strl.Substring(14);
|
|
|
|
|
// JenkIndex.Ensure(strl);
|
|
|
|
|
//}
|
2017-12-23 04:56:39 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-07 02:41:10 +08:00
|
|
|
|
//int startIndex = 0;
|
|
|
|
|
//for (int i = 0; i < rawData.Length; i++)
|
|
|
|
|
//{
|
|
|
|
|
// byte c = rawData[i];
|
|
|
|
|
// if (c == 0)
|
|
|
|
|
// {
|
|
|
|
|
// string str = Encoding.ASCII.GetString(rawData.AsSpan(startIndex, i - startIndex));
|
|
|
|
|
// if (!string.IsNullOrEmpty(str))
|
|
|
|
|
// {
|
|
|
|
|
// //JenkIndex.Ensure(str);
|
|
|
|
|
|
|
|
|
|
// JenkIndex.EnsureLower(str);
|
|
|
|
|
|
|
|
|
|
// ////DirMod_Sounds_ entries apparently can be used to infer SP audio strings
|
|
|
|
|
// ////no luck here yet though
|
|
|
|
|
// //if (strl.StartsWith("dirmod_sounds_") && (strl.Length > 14))
|
|
|
|
|
// //{
|
|
|
|
|
// // strl = strl.Substring(14);
|
|
|
|
|
// // JenkIndex.Ensure(strl);
|
|
|
|
|
// //}
|
|
|
|
|
// }
|
|
|
|
|
// startIndex = i + 1;
|
|
|
|
|
// }
|
|
|
|
|
//}
|
2017-12-23 04:56:39 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2023-10-28 03:31:09 +08:00
|
|
|
|
catch(Exception err)
|
2017-09-21 18:33:05 +08:00
|
|
|
|
{
|
2023-10-28 03:31:09 +08:00
|
|
|
|
ErrorLog?.Invoke(err.ToString());
|
2023-11-14 23:16:59 +08:00
|
|
|
|
Console.WriteLine(err.ToString());
|
2017-09-21 18:33:05 +08:00
|
|
|
|
//failing silently!! not so good really
|
|
|
|
|
}
|
2023-10-28 03:31:09 +08:00
|
|
|
|
});
|
|
|
|
|
//foreach (RpfFile file in AllRpfs)
|
|
|
|
|
//{
|
|
|
|
|
|
|
|
|
|
//}
|
2020-02-08 03:24:04 +08:00
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 100; i++)
|
|
|
|
|
{
|
|
|
|
|
JenkIndex.Ensure(i.ToString("00"));
|
|
|
|
|
}
|
2023-11-14 23:16:59 +08:00
|
|
|
|
|
2024-01-07 02:41:10 +08:00
|
|
|
|
_ = Task.Run(() => {
|
|
|
|
|
using var timer2 = new DisposableTimer("BuildBaseJenkIndex -> AddAllLods");
|
|
|
|
|
foreach (var name in yddFiles)
|
|
|
|
|
{
|
|
|
|
|
AddAllLods(name);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2023-11-14 23:16:59 +08:00
|
|
|
|
//Task.Run(() =>
|
|
|
|
|
//{
|
|
|
|
|
// foreach (var count in counts.OrderBy(p => p.Value))
|
|
|
|
|
// {
|
|
|
|
|
// Console.WriteLine($"{count.Key,30}: {count.Value,3}");
|
|
|
|
|
// }
|
|
|
|
|
//});
|
2017-09-21 18:33:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|