Skip to content

Commit

Permalink
Add first public source
Browse files Browse the repository at this point in the history
  • Loading branch information
jzebedee committed Feb 12, 2019
1 parent e7b2f42 commit 0edbf34
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 0 deletions.
21 changes: 21 additions & 0 deletions src/LibCK2/LibCK2.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<NullableReferenceTypes>true</NullableReferenceTypes>
<Company>scorpdx</Company>
<Authors>jzebedee</Authors>
<Description>LibCK2 is a .NET Standard library for manipulating Crusader Kings 2 game data and save games</Description>
<Copyright></Copyright>
<PackageLicenseUrl></PackageLicenseUrl>
<PackageProjectUrl>https://github.com/scorpdx/LibCK2</PackageProjectUrl>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.0.0</Version>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="Microsoft.Experimental.Collections" Version="1.0.6-e190117-3" />
</ItemGroup>

</Project>
121 changes: 121 additions & 0 deletions src/LibCK2/SaveGame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using Microsoft.Collections.Extensions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace LibCK2
{
/// <summary>
/// A model of a Crusader Kings 2 saved game
/// </summary>
public class SaveGame
{
private const string SavePathSuffix = @"Paradox Interactive\Crusader Kings II\save games";
private const string CK2Header = "CK2txt";

private static readonly Regex r_nesting = new Regex(@"^(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+?(?(Open)(?!))$", RegexOptions.Compiled | RegexOptions.Multiline);
private static readonly Regex r_valuePair = new Regex(@"(?<Identifier>\w+)=(?<Value>[^{}\n]*)", RegexOptions.Compiled);
private static readonly Regex r_dataList = new Regex(@"^\s*(?:(\S+)\s?)*$", RegexOptions.Compiled | RegexOptions.Multiline);

public static string SaveGameLocation
{
get
{
string MyDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
return Path.Combine(MyDocuments, SavePathSuffix);
}
}

public IReadOnlyDictionary<string, IReadOnlyCollection<object>> GameState { get; }

public SaveGame(string text)
{
GameState = Parse(text);
}

private MultiValueDictionary<string, object> Parse(string text)
{
if (!text.StartsWith(CK2Header))
{
throw new InvalidOperationException($"Not a {CK2Header} file");
}

var matches = r_nesting.Matches(text).OfType<Match>();
var Tree = new MultiValueDictionary<string, object>();
TopLevelLoop(Tree, matches.Skip(1)); //skip ck2txt header

return Tree;
}

private void TopLevelLoop(MultiValueDictionary<string, object> cur, IEnumerable<Match> matches)
{
Lazy<MultiValueDictionary<string, object>> CreateLazyDepth(string searchSpace)
{
return new Lazy<MultiValueDictionary<string, object>>(() =>
{
var nested = new MultiValueDictionary<string, object>();
TopLevelLoop(nested, r_nesting.Matches(searchSpace).OfType<Match>());

return nested;
});
}

string subNestId = null;
foreach (Match match in matches)
{
if (string.IsNullOrWhiteSpace(match.Value)) continue;

bool isNested = match.Groups["Content"].Success;

//always try to match a pair
var vp = r_valuePair.Match(match.Value);
var id = vp.Groups["Identifier"].Value;
var value = vp.Groups["Value"].Value;
if (!vp.Success && !isNested)
{
var dl = r_dataList.Match(match.Value);
if (dl.Success)
{
cur.Add(id, dl.Groups[1].Captures.OfType<Capture>().Select(c => c.Value).ToList());
}
else throw new InvalidOperationException("Unknown field");
}
else
{
bool hasValue = vp.Groups["Value"].Success && !string.IsNullOrEmpty(value);
if (isNested)
{
//split-level
if (subNestId != null)
{
var nc = match.Groups["Content"].Value;
cur.Add(subNestId, CreateLazyDepth(nc));
subNestId = null;
}
//same-level
else
{
var nc = match.Groups["Content"].Value;
cur.Add(id, CreateLazyDepth(nc));
}
}
else if (vp.Success)
{
if (!string.IsNullOrEmpty(value))
{
cur.Add(id, value);
}
else
{
if (subNestId != null) throw new InvalidOperationException("Nested content was not expanded before reaching next field");

subNestId = id;
}
}
}
}
}
}
}

0 comments on commit 0edbf34

Please sign in to comment.