Compare commits

..

10 Commits

Author SHA1 Message Date
d609b409f9 updated dependencies 2025-07-04 23:45:45 +03:00
6bdf66b326 added paradox-dlc-metadata-parser project 2025-07-04 22:53:52 +03:00
0c2670f55a dotnet 8 2024-01-01 15:15:11 +06:00
7fe79f0300 New logging and Irony integration 2023-09-28 14:11:34 +06:00
126d29313e fixed bug 2023-04-08 00:26:59 +06:00
3df5b62a13 fixed Append to modlist 2023-04-07 22:20:19 +06:00
9a2d29d320 code cleanup 2023-04-07 21:08:36 +06:00
e59dfec8b5 Fixed Localisation functions 2023-04-07 21:08:28 +06:00
e781df8562 copy new mods to !new_$time 2023-04-07 18:45:59 +06:00
810a4fa8fc YesAll 2023-04-05 21:56:41 +06:00
14 changed files with 418 additions and 172 deletions

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using DiffMatchPatch; using DiffMatchPatch;
using DTLib.Ben.Demystifier;
using DTLib.Console; using DTLib.Console;
using DTLib.Filesystem; using DTLib.Filesystem;
@ -26,17 +25,20 @@ public static class DiffText
List<Diff>? diff = null; List<Diff>? diff = null;
bool noColors = false; bool noColors = false;
new LaunchArgumentParser( new LaunchArgumentParser(
new LaunchArgument(new[] { "s", "string" }, new LaunchArgument(["s", "string"],
"shows difference of two strings", "shows difference of two strings",
(s0, s1) => diff=TextDiff(s0, s1), (s0, s1) => diff=TextDiff(s0, s1),
"string0", "string1", 1), "string0", "string1",
new LaunchArgument(new[] { "f", "file" }, 1),
"shows difference of two text files", new LaunchArgument(["f", "file"],
(f0,f1) => diff=FileDiff(f0, f1), "shows difference of two text files",
"file0", "file1", 1), (f0,f1) => diff=FileDiff(f0, f1),
new LaunchArgument(new []{"p", "plain-text","no-colors"}, "file0", "file1",
"print diff in plain text format", 1),
()=> noColors=true, 0) new LaunchArgument(["p", "plain-text","no-colors"],
"print diff in plain text format",
()=> noColors=true,
0)
).ParseAndHandle(args); ).ParseAndHandle(args);
if (diff == null) if (diff == null)
throw new Exception("no action specified: use -s or -f"); throw new Exception("no action specified: use -s or -f");
@ -46,7 +48,7 @@ public static class DiffText
{ } { }
catch (Exception ex) catch (Exception ex)
{ {
ColoredConsole.WriteLine("r", ex.ToStringDemystified()); ColoredConsole.WriteLine("r", $"{ex.Message} at {ex.Source}");
return 1; return 1;
} }

View File

@ -1,21 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RootNamespace>diff_text</RootNamespace> <LangVersion>12</LangVersion>
<ImplicitUsings>disable</ImplicitUsings> <ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>diff_text</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DTLib.Ben.Demystifier" Version="1.0.4" /> <PackageReference Include="google-diff-match-patch" Version="1.3.100" />
<PackageReference Include="google-diff-match-patch" Version="1.3.74" /> <PackageReference Include="DTLib" Version="1.4.3" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\..\DTLib\DTLib\DTLib.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.2.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DTLib.Demystifier;
using DTLib.Console;
using DTLib.Extensions;
using File = DTLib.Filesystem.File;
namespace ParadoxDlcMetadataParser;
static class Program
{
private static string? GameName;
public static void Main(string[] args)
{
try
{
new LaunchArgumentParser(
new LaunchArgument(
new[] { "f", "file" },
"Searches for dlc id and name declarations in a dlc_metadata/* file.",
ParseFile,
"file_name",
priority: 99),
new LaunchArgument(
new[] { "g", "game" },
"Sets game name.",
name => GameName = name,
"game_name")
).ParseAndHandle(args);
}
catch (LaunchArgumentParser.ExitAfterHelpException)
{
// this exception is thrown when the program exits after showing help message
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.ToStringDemystified());
}
Console.ResetColor();
}
static void ParseFile(string filePath)
{
using var file = File.OpenRead(filePath);
using var r = new StreamReader(file);
List<string> names = new();
List<int> ids = new();
while (!r.EndOfStream)
{
string line = r.ReadLine()!;
if (IsVarDeclaration(line, "name"))
names.Add(line.AsSpan().After('=').After('"').Before('"').Trim().Trim('\t').ToString());
else if (IsVarDeclaration(line, "id") || IsVarDeclaration(line, "steam_id"))
{
var s1 = line.AsSpan().After('=');
if(s1.Contains('#'))
s1 = s1.After("#"); // some ids are hidden in comments
ids.Add(Convert.ToInt32(s1.Trim().Trim('\t').Trim('"').Trim(' ').ToString()));
}
}
if (names.Count != ids.Count)
throw new Exception("names.Count != ids.Count");
var ids_and_names = ids.Zip(names)
.DistinctBy(tuple => tuple.First)
.OrderBy(tuple => tuple.First)
.ToList<(int id, string name)>();
Console.WriteLine("----------------------------------");
foreach ((int id, string name) in ids_and_names)
{
Console.WriteLine($"{id} = {GameName}: {name}");
}
Console.WriteLine("----------------------------------");
Console.WriteLine($"{ids_and_names.Count} DLC declarations found");
}
static bool IsVarDeclaration(string line, string varName) =>
line.StartsWith($"{varName} =") ||
line.StartsWith($"{varName}=") ||
line.Contains($" {varName} =") ||
line.Contains($" {varName}=") ||
line.Contains($"\t{varName} =") ||
line.Contains($"\t{varName}=");
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<RootNamespace>ParadoxDlcMetadataParser</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DTLib" Version="1.7.4" />
<PackageReference Include="DTLib.Demystifier" Version="1.1.1" />
</ItemGroup>
</Project>

View File

@ -15,10 +15,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution_files", "solution_
Makefile = Makefile Makefile = Makefile
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib", "..\DTLib\DTLib\DTLib.csproj", "{67E226B7-F04B-4FB1-A9AA-E4AE3A5A8A3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "diff-text", "diff-text\diff-text.csproj", "{720D8D44-A9D3-4F58-BA1E-EA95808D1376}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "diff-text", "diff-text\diff-text.csproj", "{720D8D44-A9D3-4F58-BA1E-EA95808D1376}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "paradox-dlc-metadata-parser", "paradox-dlc-metadata-parser\paradox-dlc-metadata-parser.csproj", "{8663EF88-08DE-4641-AAAE-9FBB54BFF66C}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
@ -29,14 +29,14 @@ Global
{076BFCFF-1D3E-44FB-B434-73716B79A135}.Release|Any CPU.Build.0 = Release|Any CPU {076BFCFF-1D3E-44FB-B434-73716B79A135}.Release|Any CPU.Build.0 = Release|Any CPU
{076BFCFF-1D3E-44FB-B434-73716B79A135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {076BFCFF-1D3E-44FB-B434-73716B79A135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{076BFCFF-1D3E-44FB-B434-73716B79A135}.Debug|Any CPU.Build.0 = Debug|Any CPU {076BFCFF-1D3E-44FB-B434-73716B79A135}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67E226B7-F04B-4FB1-A9AA-E4AE3A5A8A3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67E226B7-F04B-4FB1-A9AA-E4AE3A5A8A3F}.Release|Any CPU.Build.0 = Release|Any CPU
{67E226B7-F04B-4FB1-A9AA-E4AE3A5A8A3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67E226B7-F04B-4FB1-A9AA-E4AE3A5A8A3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{720D8D44-A9D3-4F58-BA1E-EA95808D1376}.Release|Any CPU.ActiveCfg = Release|Any CPU {720D8D44-A9D3-4F58-BA1E-EA95808D1376}.Release|Any CPU.ActiveCfg = Release|Any CPU
{720D8D44-A9D3-4F58-BA1E-EA95808D1376}.Release|Any CPU.Build.0 = Release|Any CPU {720D8D44-A9D3-4F58-BA1E-EA95808D1376}.Release|Any CPU.Build.0 = Release|Any CPU
{720D8D44-A9D3-4F58-BA1E-EA95808D1376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {720D8D44-A9D3-4F58-BA1E-EA95808D1376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{720D8D44-A9D3-4F58-BA1E-EA95808D1376}.Debug|Any CPU.Build.0 = Debug|Any CPU {720D8D44-A9D3-4F58-BA1E-EA95808D1376}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8663EF88-08DE-4641-AAAE-9FBB54BFF66C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8663EF88-08DE-4641-AAAE-9FBB54BFF66C}.Release|Any CPU.Build.0 = Release|Any CPU
{8663EF88-08DE-4641-AAAE-9FBB54BFF66C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8663EF88-08DE-4641-AAAE-9FBB54BFF66C}.Debug|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1,8 +1,6 @@
using System.Linq;
using DTLib.Console;
using DTLib.Dtsod;
using diff_text; using diff_text;
using DTLib.Ben.Demystifier; using DTLib.Dtsod;
using DTLib.XXHash;
namespace ParadoxModMerger; namespace ParadoxModMerger;
@ -17,8 +15,8 @@ public record struct ConflictingModFile(string FilePath, string[] Mods);
static class Diff static class Diff
{ {
static ConsoleLogger logger = new($"logs", "diff"); static ContextLogger logger = new (nameof(Diff), new FileLogger("logs", "diff"));
static void Log(params string[] msg) => logger.Log(msg); static void Log(params string[] msg) => logger.LogColored(msg);
public static void DiffCommandHandler(string connected_pathes) public static void DiffCommandHandler(string connected_pathes)
{ {
@ -73,9 +71,7 @@ static class Diff
line_i = 0; line_i = 0;
for (int confl_i = 0; confl_i < conflicts.Length; confl_i++) for (int confl_i = 0; confl_i < conflicts.Length; confl_i++)
{ {
if (confl_i == selected_confl_i) lines[line_i].color = confl_i == selected_confl_i ? ConsoleColor.Blue : ConsoleColor.White;
lines[line_i].color = ConsoleColor.Blue;
else lines[line_i].color = ConsoleColor.White;
line_i++; line_i++;
for (int mod_i = 0; mod_i < conflicts[confl_i].Mods.Length; mod_i++) for (int mod_i = 0; mod_i < conflicts[confl_i].Mods.Length; mod_i++)
@ -180,7 +176,7 @@ static class Diff
var file = _file.RemoveBase(modp); var file = _file.RemoveBase(modp);
if (all_files.TryGetValue(file.Str, out var associated_mods)) if (all_files.TryGetValue(file.Str, out var associated_mods))
associated_mods.Add(modp.Str); associated_mods.Add(modp.Str);
else all_files.Add(file.Str, new List<string>(1) { modp.Str }); else all_files.Add(file.Str, [modp.Str]);
} }
} }
@ -210,9 +206,15 @@ static class Diff
{ {
if (filePathDiff.State == DiffState.Equal) if (filePathDiff.State == DiffState.Equal)
{ {
Hasher hasher = new Hasher(); ulong hash0, hash1;
string hash0=hasher.HashFile(Path.Concat(dir0, filePathDiff.Value)).HashToString(); using (var file0 = File.OpenRead(Path.Concat(dir0, filePathDiff.Value)))
string hash1=hasher.HashFile(Path.Concat(dir1, filePathDiff.Value)).HashToString(); {
hash0 = xxHash64.ComputeHash(file0);
}
using (var file1 = File.OpenRead(Path.Concat(dir1, filePathDiff.Value)))
{
hash1 = xxHash64.ComputeHash(file1);
}
if (hash0 != hash1) if (hash0 != hash1)
yield return filePathDiff with { State = DiffState.Changed }; yield return filePathDiff with { State = DiffState.Changed };
else yield return filePathDiff; else yield return filePathDiff;

View File

@ -0,0 +1,79 @@
namespace ParadoxModMerger;
public static class IronyIntegration
{
static ContextLogger logger = new(nameof(IronyIntegration), new FileLogger("logs", "irony-integration"));
static void Log(params string[] msg) => logger.LogColored(msg);
public static void GenerateIronyCollection(string dirs_with_mods_connected, IOPath out_json_file_path)
{
IOPath[] dirs_with_mods = Program.SplitArgToPaths(dirs_with_mods_connected, true);
var mod_desc_values = new List<(string name, string steam_id)>();
foreach (var dir in dirs_with_mods)
{
foreach (var mod_dir in Directory.GetDirectories(dir))
{
IOPath descriptor_path = Path.Concat(mod_dir, "descriptor.mod");
if (!File.Exists(descriptor_path))
Log("y", "directory ", "c", mod_dir.Str, "y", " doesn't contain descriptor");
else
{
string? name=null, remote_file_id=null;
var lines = System.IO.File.ReadAllLines(descriptor_path.Str);
foreach (var line in lines)
{
if (line.StartsWith("name="))
{
// "name=\"".Length==6
name = line.Substring(6, line.LastIndexOf('\"')-6);
}
else if (line.StartsWith("remote_file_id="))
{
// "remote_file_id=\"".Length==16
remote_file_id = line.Substring(16, line.LastIndexOf('\"')-16);
}
}
if (string.IsNullOrEmpty(name))
throw new NullReferenceException("name=null");
if (string.IsNullOrEmpty(remote_file_id))
throw new NullReferenceException("remote_file_id=null");
mod_desc_values.Add((name,remote_file_id)!);
Log("b",$"[{mod_desc_values.Count-1}] {{ ", "c", name!, "b", $", {remote_file_id!} }}");
}
}
}
using var out_json_stream = File.OpenWrite(out_json_file_path);
using var stream_writer = new System.IO.StreamWriter(out_json_stream, StringConverter.UTF8);
stream_writer.WriteLine($$"""
{
"game":"stellaris",
"name":"{{out_json_file_path.LastName().AsSpan().BeforeLast('.')}}",
"mods":[
""");
for (int mod_pos = 0; mod_pos < mod_desc_values.Count; mod_pos++)
{
stream_writer.Write($$"""
{
"displayName":"{{mod_desc_values[mod_pos].name}}",
"enabled":true,
"position":{{mod_pos}},
"steamId":"{{mod_desc_values[mod_pos].steam_id}}"
}
""");
if(mod_pos<mod_desc_values.Count-1)
stream_writer.Write(',');
stream_writer.Write('\n');
}
stream_writer.WriteLine("""
]
}
""");
stream_writer.Flush();
stream_writer.Close();
}
}

View File

@ -2,81 +2,108 @@
static class Localisation static class Localisation
{ {
static ConsoleLogger logger = new($"logs", "autoloc"); static ContextLogger logger = new(nameof(Localisation), new FileLogger("logs", "autoloc"));
static void Log(params string[] msg) => logger.Log(msg); static void Log(params string[] msg) => logger.LogColored(msg);
public static void GenerateRussian(IOPath engDir, IOPath rusDir) public static void GenerateRussian(IOPath _engDir, IOPath _rusDir)
{ {
int counter = 0; int counter = 0;
foreach (var fileName in Directory.GetAllFiles(engDir)) ProcessDir(_engDir, _rusDir);
{
if (!fileName.EndsWith("l_english.yml"))
continue;
IOPath rusFileName = fileName
.ReplaceBase(engDir, rusDir)
.Replace("l_english", "l_russian");
if (!File.Exists(rusFileName))
{
Log("gray", $"skipped file {rusFileName.RemoveBase(rusDir)}");
continue;
}
string text = File.ReadAllText(fileName)
.Replace("l_english:", "l_russian: ");
byte[] bytes = StringConverter.UTF8BOM.GetBytes(text);
File.WriteAllBytes(rusFileName, bytes);
Log("g", $"file {rusFileName} created");
counter++;
}
Log("g",$"created {counter} localisation files"); Log("g",$"created {counter} localisation files");
void ProcessDir(IOPath engDir, IOPath rusDir)
{
foreach (var fileName in Directory.GetFiles(engDir))
{
if (!fileName.EndsWith("l_english.yml"))
continue;
IOPath rusFileName = fileName
.ReplaceBase(engDir, rusDir)
.Replace("l_english", "l_russian");
if (File.Exists(rusFileName))
{
// Log("w", $"skipped {rusFileName.RemoveBase(rusDir)}");
continue;
}
string text = File.ReadAllText(fileName)
.Replace("l_english:", "l_russian: ");
byte[] bytes = StringConverter.UTF8BOM.GetBytes(text);
File.WriteAllBytes(rusFileName, bytes);
Log("g", $"created {rusFileName}");
counter++;
}
void ProcessSubdir(string subdirEngName, string subdirRusName)
{
var subdirEng = Path.Concat(engDir,subdirEngName);
var subdirRus = Path.Concat(rusDir,subdirRusName);
if (Directory.Exists(subdirEng))
ProcessDir(subdirEng, subdirRus);
}
ProcessSubdir("english", "russian");
ProcessSubdir("replace", "replace");
ProcessSubdir("name_lists", "name_lists");
ProcessSubdir("random_names", "random_names");
}
} }
// deletes all localisations except l_russian and l_english // deletes all localisations except l_russian and l_english
public static void Clean(IOPath _loc_dir) public static void Clean(IOPath _loc_dir)
{ {
Log("g", $"deleted {RemoveUnneededDirs(_loc_dir)} dirs"); int deleted_files_count=0, deleted_dirs_count=0;
Log("g", $"deleted {RemoveUnneededFiles(_loc_dir)} files"); DeleteUnneededDirs(_loc_dir);
DeleteUnneededFiles(_loc_dir);
Log("g", $"deleted {deleted_files_count} files");
Log("g", $"deleted {deleted_dirs_count} dirs");
void DeleteUnneededDirs(IOPath loc_dir)
int RemoveUnneededDirs(IOPath loc_dir)
{ {
int count = 0;
foreach (var subdir in Directory.GetDirectories(loc_dir)) foreach (var subdir in Directory.GetDirectories(loc_dir))
{ {
string dir_basename = subdir.LastName().Str; string dir_basename = subdir.LastName().Str;
if (dir_basename == "russian" || dir_basename == "english") if (dir_basename is "russian" or "english")
continue;
if (dir_basename == "replace")
{ {
RemoveUnneededDirs(subdir); // Log("w",$"skipped {subdir}");
RemoveUnneededFiles(subdir);
continue; continue;
} }
if (dir_basename.Contains("rus")) if (dir_basename is "replace" or "name_lists" or "random_names")
Log("y", $"unexpected dir: {subdir}"); {
Directory.Delete(subdir); DeleteUnneededDirs(subdir);
count++; DeleteUnneededFiles(subdir);
} continue;
}
return count; // incorrect dirs, for example l_russian/
if (dir_basename.ToLower().Contains("russian") || dir_basename.ToLower().Contains("english"))
Log("y", $"unexpected dir: {subdir}");
Directory.Delete(subdir);
Log("m", $"deleted {subdir}");
deleted_dirs_count++;
}
} }
int RemoveUnneededFiles(IOPath loc_dir) void DeleteUnneededFiles(IOPath loc_dir)
{ {
int count = 0;
foreach (var file in Directory.GetFiles(loc_dir)) foreach (var file in Directory.GetFiles(loc_dir))
{ {
if(file.EndsWith("l_russian") || file.EndsWith("l_enghish")) if(file.EndsWith("l_russian.yml") || file.EndsWith("l_english.yml"))
{
// Log("w",$"skipped {file}");
continue; continue;
}
if (!file.Contains("_l_") || !file.EndsWith(".yml")) if (!file.Contains("_l_") || !file.EndsWith(".yml"))
Log("y",$"unexpected file: {file}"); Log("y",$"unexpected file: {file}");
File.Delete(file); File.Delete(file);
count++; Log("m",$"deleted {file}");
deleted_files_count++;
} }
return count;
} }
} }
} }

View File

@ -1,12 +1,9 @@
using System.Linq; namespace ParadoxModMerger;
using DTLib.Console;
namespace ParadoxModMerger;
static class Merge static class Merge
{ {
static ConsoleLogger logger = new($"logs", "merge"); static ContextLogger logger = new(nameof(Merge), new FileLogger("logs", "merge"));
static void Log(params string[] msg) => logger.Log(msg); static void Log(params string[] msg) => logger.LogColored(msg);
private const string modlist_filename = "modlist.txt"; private const string modlist_filename = "modlist.txt";
@ -18,11 +15,14 @@ static class Merge
for (int i = 0; i < files.Count; i++) for (int i = 0; i < files.Count; i++)
{ {
string file_basename = files[i].LastName().Str; string file_basename = files[i].LastName().Str;
if(file_basename=="descriptor.mod") // skip file if (file_basename=="descriptor.mod") // skip file
continue; continue;
if (file_basename == modlist_filename) // append modlist if (file_basename == modlist_filename) // append modlist
{ {
File.AppendAllText(out_modlist_file, File.ReadAllText(files[i])); string subModlistText = File.ReadAllText(files[i]);
File.AppendAllText(out_modlist_file, subModlistText);
continue;
} }
var newfile = files[i].ReplaceBase(srcDir, outDir); var newfile = files[i].ReplaceBase(srcDir, outDir);
@ -50,18 +50,31 @@ static class Merge
public static void MergeInto(IOPath moddir, IOPath outDir) public static void MergeInto(IOPath moddir, IOPath outDir)
{ {
HandleConflicts(new[] { moddir, outDir }); HandleConflicts([moddir, outDir]);
IOPath out_modlist_file = Path.Concat(outDir, modlist_filename); IOPath out_modlist_file = Path.Concat(outDir, modlist_filename);
ModDirCopy(moddir, outDir, modlist_filename);
File.AppendAllText(out_modlist_file, $"{moddir.LastName()}\n"); File.AppendAllText(out_modlist_file, $"{moddir.LastName()}\n");
ModDirCopy(moddir, outDir, out_modlist_file);
} }
public static void ConsoleAskYN(string question, Action? yes, Action? no) public static void ConsoleAskYN(string question, Action? yes, Action? no)
{ {
Log("y", question + " [y/n]"); while (true)
string answ = ColoredConsole.Read("w").ToLower(); {
if (answ == "y") yes?.Invoke(); Log("y", question + " [y/n]");
else no?.Invoke(); string answ = Program.YesAll ? "y" : (ColoredConsole.Read("w") ?? "").ToLower();
if (answ == "y")
{
Log("c",$"answer: {answ}");
yes?.Invoke();
break;
}
if (answ == "n") {
Log("c",$"answer: {answ}");
no?.Invoke();
break;
}
Log("r", $"incorrect answer: {answ}");
}
} }
static void HandleConflicts(IOPath[] moddirs) static void HandleConflicts(IOPath[] moddirs)
@ -89,9 +102,9 @@ static class Merge
public static void UpdateMods(IOPath updated_mods_dir, IOPath[] outdated_dirs, IOPath backup_dir) public static void UpdateMods(IOPath updated_mods_dir, IOPath[] outdated_dirs, IOPath backup_dir)
{ {
var src_dir_mods = Directory.GetDirectories(updated_mods_dir).ToList(); var src_dir_mods = Directory.GetDirectories(updated_mods_dir).ToList();
List<IOPath> not_found_mods = new List<IOPath>(); List<IOPath> not_found_mods = [];
List<IOPath> changed_mods = new List<IOPath>(); List<IOPath> changed_mods = [];
List<IOPath> unchanged_mods = new List<IOPath>(); List<IOPath> unchanged_mods = [];
foreach (IOPath outdated_mods_dir in outdated_dirs) foreach (IOPath outdated_mods_dir in outdated_dirs)
foreach (var mod in Directory.GetDirectories(outdated_mods_dir)) foreach (var mod in Directory.GetDirectories(outdated_mods_dir))
@ -145,8 +158,8 @@ static class Merge
} }
List<IOPath> added_mods = new List<IOPath>(src_dir_mods.Count - changed_mods.Count); List<IOPath> added_mods = new List<IOPath>(src_dir_mods.Count - changed_mods.Count);
var found_mods = Enumerable var found_mods = changed_mods
.Concat(changed_mods, unchanged_mods) .Concat(unchanged_mods)
.Select(m=>Path.Concat(updated_mods_dir, m.LastName())) .Select(m=>Path.Concat(updated_mods_dir, m.LastName()))
.ToList(); .ToList();
foreach (var modD in Diff.DiffCollections(found_mods, src_dir_mods)) foreach (var modD in Diff.DiffCollections(found_mods, src_dir_mods))
@ -167,6 +180,17 @@ static class Merge
"m", not_found_mods.MergeToString('\n')); "m", not_found_mods.MergeToString('\n'));
if (unchanged_mods.Count>0) if (unchanged_mods.Count>0)
Log("w", $"unchanged {unchanged_mods.Count}"); Log("w", $"unchanged {unchanged_mods.Count}");
IOPath new_mods_copy_dir = Path.Concat(backup_dir.ParentDir(),
$"!new_{DateTime.Now.ToString(MyTimeFormat.ForFileNames)}");
ConsoleAskYN($"copy new mods to {new_mods_copy_dir}", () =>
{
foreach (var mod in added_mods)
{
Directory.Copy(mod, Path.Concat(new_mods_copy_dir, mod), false);
}
},
null);
} }
public static void RenameModsCommandHandler(string dir_with_mods, string rename_pairs_str) public static void RenameModsCommandHandler(string dir_with_mods, string rename_pairs_str)

View File

@ -1,21 +1,36 @@
global using System; global using System;
global using System.Collections.Generic; global using System.Collections.Generic;
global using System.Diagnostics; global using System.Diagnostics;
global using System.Linq;
global using System.Text; global using System.Text;
global using System.Threading.Tasks;
global using DTLib; global using DTLib;
global using DTLib.Extensions; global using DTLib.Extensions;
global using DTLib.Filesystem; global using DTLib.Filesystem;
global using DTLib.Logging; global using DTLib.Logging;
using System.Linq; global using DTLib.Demystifier;
using System.Security.Cryptography; global using DTLib.Console;
using DTLib.Console;
namespace ParadoxModMerger; namespace ParadoxModMerger;
public static class Program public static class Program
{ {
static ConsoleLogger logger = new($"logs", "main"); static ContextLogger logger = new ContextLogger(nameof(Program), new FileLogger("logs", "main"));
static void Log(params string[] msg) => logger.Log(msg); static void Log(params string[] msg) => logger.LogColored(msg);
public static void LogColored(this ContextLogger _logger, params string[] msg)
{
ColoredConsole.WriteLine(msg);
StringBuilder b = new();
if (msg.Length == 1)
b.Append(msg[0]);
else for (int i = 1; i < msg.Length; i+=2)
b.Append(msg[i]);
_logger.LogInfo(b.ToString());
}
public static bool YesAll;
static int Main(string[] args) static int Main(string[] args)
{ {
@ -27,61 +42,71 @@ public static class Program
string outPath = "" ; string outPath = "" ;
new LaunchArgumentParser( new LaunchArgumentParser(
new LaunchArgument(new []{"o", "out"}, new LaunchArgument(["o", "out"],
"sets output path", "Sets output path",
p => outPath=p, p => outPath = p,
"out_path", "out_path",
0), 0),
new LaunchArgument(new []{"clear"}, new LaunchArgument(["y", "yes-all"],
"Automatically answers [Y] to all questions",
() => YesAll = true,
0),
new LaunchArgument(["clear"],
"Clear mod files and put them into separate dirs in output dir. Requires -o", "Clear mod files and put them into separate dirs in output dir. Requires -o",
wdir=>Workshop.ClearWorkshop(wdir, outPath), wdir => Workshop.ClearWorkshop(wdir, outPath),
"workshop_dir", "workshop_dir",
1), 1),
new LaunchArgument(new []{"diff"}, new LaunchArgument(["diff"],
"Compares mod files by hash", "Compares mod files by hash",
p=>Diff.DiffCommandHandler(p), p => Diff.DiffCommandHandler(p),
"first_mod_directory:second_mod_directory:...", "first_mod_directory:second_mod_directory:...",
1), 1),
new LaunchArgument(new []{"diff-detailed"}, new LaunchArgument(["diff-detailed"],
"reads conflicts_XXX.dtsod file and shows text diff for each file", "reads conflicts_XXX.dtsod file and shows text diff for each file",
p=>Diff.DiffDetailedCommandHandler(p), p => Diff.DiffDetailedCommandHandler(p),
"conflicts_dtsod_path", "conflicts_dtsod_path",
1), 1),
new LaunchArgument(new []{"merge-subdirs"}, new LaunchArgument(["merge-subdirs"],
"Merges mods and shows conflicts. Requires -o", "Merges mods and shows conflicts. Requires -o",
d => Merge.MergeAll(Directory.GetDirectories(d), outPath), d => Merge.MergeAll(Directory.GetDirectories(d), outPath),
"dir_with_mods", "dir_with_mods",
1), 1),
new LaunchArgument(new []{"merge-into", "merge-single"}, new LaunchArgument(["merge-into", "merge-single"],
"Merges one mod into output dir and shows conflicts. Requires -o", "Merges one mod into output dir and shows conflicts. Requires -o",
mod=>Merge.MergeInto(mod, outPath), mod => Merge.MergeInto(mod, outPath),
"mod_dir", "mod_dir",
1), 1),
new LaunchArgument(new []{"gen-rus-locale"}, new LaunchArgument(["gen-rus-locale"],
"Creates l_russian copy of english locale in output directory. Requires -o", "Creates l_russian copy of english locale in output directory. Requires -o",
eng=>Localisation.GenerateRussian(eng, outPath), eng => Localisation.GenerateRussian(eng, outPath),
"english_locale_path", "english_locale_path",
1), 1),
new LaunchArgument(new []{"desc"}, new LaunchArgument(["desc"],
"Downloads mod description from steam to new file in outDir. Requires -o", "Downloads mod description from steam to new file in outDir. Requires -o",
id=>Workshop.CreateDescFile(id, outPath).GetAwaiter().GetResult(), id => Workshop.CreateDescFile(id, outPath).GetAwaiter().GetResult(),
"mod_id", "mod_id",
1), 1),
new LaunchArgument(new []{"rename"}, new LaunchArgument(["rename"],
"Renames mods in directory", "Renames mods in directory",
(modsdir, replace_pairs)=>Merge.RenameModsCommandHandler(modsdir, replace_pairs), (modsdir, replace_pairs) => Merge.RenameModsCommandHandler(modsdir, replace_pairs),
"dir_with_mods", "replace_pairs (old_name:new_name:...)", "dir_with_mods", "replace_pairs (old_name:new_name:...)",
1), 1),
new LaunchArgument(new []{"update-mods"}, new LaunchArgument(["update-mods"],
"Updates mods in [outdated_dir0...outdated_dirN] to new versions if found in updated_mods_dir. " + "Updates mods in [outdated_dir0...outdated_dirN] to new versions if found in updated_mods_dir. " +
"Moves old mods to backup_dir defined by -o.", "Moves old mods to backup_dir defined by -o.",
(updated, outdated)=>Merge.UpdateMods(updated, SplitArgToPaths(outdated, true), outPath), (updated, outdated) => Merge.UpdateMods(updated, SplitArgToPaths(outdated, true), outPath),
"updated_mods_dir", "outdated_dir OR outdated_dir0:...:outdated_dirN", "updated_mods_dir", "outdated_dir OR outdated_dir0:...:outdated_dirN",
1), 1),
new LaunchArgument(new []{"clean-locales"}, new LaunchArgument(["clean-locales"],
"Deletes all localisations except l_russian and l_english.", "Deletes all localisations except l_russian and l_english.",
locdir=> Localisation.Clean(locdir), locdir => Localisation.Clean(locdir),
"localisation_dir", "localisation_dir",
1),
new LaunchArgument(["gen-collection-json"],
"Generates json file representing mod collection in format readable by pdx launcher and IronyModManager." +
"Requires -o",
(connected_dirs) => IronyIntegration.GenerateIronyCollection(connected_dirs, outPath),
"connected_dirs_with_mods",
1) 1)
).ParseAndHandle(args); ).ParseAndHandle(args);
} }
@ -89,7 +114,7 @@ public static class Program
{ } { }
catch (Exception ex) catch (Exception ex)
{ {
Log("r", DTLib.Ben.Demystifier.ExceptionExtensions.ToStringDemystified(ex)); Log("r", ExceptionExtensions.ToStringDemystified(ex));
Console.ResetColor(); Console.ResetColor();
return 1; return 1;
} }
@ -105,7 +130,7 @@ public static class Program
else if (connected_parts.Contains(';')) else if (connected_parts.Contains(';'))
part_sep = ';'; part_sep = ';';
else if (allow_one_part) else if (allow_one_part)
return new []{connected_parts}; return [connected_parts];
else throw new Exception($"<{connected_parts}> doesn't contain any separators (:/;)"); else throw new Exception($"<{connected_parts}> doesn't contain any separators (:/;)");
return connected_parts.Split(part_sep); return connected_parts.Split(part_sep);

View File

@ -1,18 +1,13 @@
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using Fizzler.Systems.HtmlAgilityPack;
using System.Threading.Tasks;
using DTLib.Ben.Demystifier;
using HtmlAgilityPack; using HtmlAgilityPack;
namespace ParadoxModMerger; namespace ParadoxModMerger;
using Fizzler.Systems.HtmlAgilityPack;
static class Workshop static class Workshop
{ {
static ContextLogger logger = new(nameof(Workshop), new FileLogger("logs", "clear"));
static ConsoleLogger logger = new($"logs", "clear"); static void Log(params string[] msg) => logger.LogColored(msg);
static void Log(params string[] msg) => logger.Log(msg);
public static void ClearWorkshop(IOPath workshopDir, IOPath outDir) public static void ClearWorkshop(IOPath workshopDir, IOPath outDir)
{ {
@ -22,7 +17,7 @@ static class Workshop
for (int i = 0; i < moddirs.Length; i++) for (int i = 0; i < moddirs.Length; i++)
{ {
string modId = moddirs[i].LastName().ToString(); string modId = moddirs[i].LastName().Str;
var zips = Directory.GetFiles(moddirs[i], "*.zip"); var zips = Directory.GetFiles(moddirs[i], "*.zip");
if (zips.Length > 0) if (zips.Length > 0)
{ {

View File

@ -2,25 +2,17 @@
<Project Sdk="Microsoft.Net.Sdk"> <Project Sdk="Microsoft.Net.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>ParadoxModMerger</RootNamespace> <RootNamespace>ParadoxModMerger</RootNamespace>
<AssemblyName>paradox-mod-merger</AssemblyName> <AssemblyName>paradox-mod-merger</AssemblyName>
<LangVersion>10</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="7z\**" CopyToOutputDirectory="PreserveNewest" /> <PackageReference Include="DTLib.XXHash" Version="1.0.4" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DTLib.Ben.Demystifier" Version="1.0.4" />
<PackageReference Include="DTLib.Dtsod" Version="1.2.0" />
<PackageReference Include="Fizzler.Systems.HtmlAgilityPack" Version="1.2.1" /> <PackageReference Include="Fizzler.Systems.HtmlAgilityPack" Version="1.2.1" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\..\DTLib\DTLib\DTLib.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.2.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\diff-text\diff-text.csproj" /> <ProjectReference Include="..\diff-text\diff-text.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
rm -rf publish rm -rf publish
mkdir publish mkdir publish
dotnet publish -c debug -o publish -f net7.0 dotnet publish -c debug -o publish

View File

@ -6,13 +6,16 @@ function publish_aot() {
echo "---------[$1]---------" echo "---------[$1]---------"
cd "$1" cd "$1"
rm -rf bin/publish rm -rf bin/publish
dotnet publish -c Release -o bin/publish -p:PublishAot=true dotnet publish -c Release -o bin/publish -p:PublishAot=true --self-contained
sleep 0.1
rm bin/publish/*.pdb
mkdir -p ../publish mkdir -p ../publish
cp -r bin/publish/* ../publish/ cp -r bin/publish/* ../publish/
cd .. cd ..
} }
rm -rf publish rm -rf publish
# paradox-mod-merger publishes diff-text as dotnet executable
#publish_aot diff-text
publish_aot paradox-mod-merger publish_aot paradox-mod-merger
publish_aot diff-text
ls -lh publish ls -lh publish