From 39108207f65e6df75ad9408bc886c4ed817a9b6f Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 5 Oct 2024 12:10:31 -0400 Subject: [PATCH] Add Dalamud plugin to collect additional data in-game There's some data we want to save that's not available on the Lodestone, for example playtime information. Now you can install the Dalamud plugin (not currently distributed) to save playtime information. In the future, we can expand this to all sorts of interesting things! --- .gitignore | 6 +- Cargo.lock | 158 +++++++++++++++++- Cargo.toml | 7 +- dalamud/Auracite.sln | 29 ++++ dalamud/Auracite/Auracite.csproj | 9 + dalamud/Auracite/Auracite.json | 8 + .../Auracite/Dalamud.Plugin.Bootstrap.targets | 12 ++ dalamud/Auracite/IStep.cs | 13 ++ dalamud/Auracite/PlaytimeStep.cs | 41 +++++ dalamud/Auracite/Plugin.cs | 89 ++++++++++ dalamud/Auracite/StepWindow.cs | 28 ++++ dalamud/Auracite/packages.lock.json | 13 ++ dalamud/global.json | 7 + src/data.rs | 1 + src/main.rs | 60 ++++++- 15 files changed, 468 insertions(+), 13 deletions(-) create mode 100644 dalamud/Auracite.sln create mode 100644 dalamud/Auracite/Auracite.csproj create mode 100644 dalamud/Auracite/Auracite.json create mode 100644 dalamud/Auracite/Dalamud.Plugin.Bootstrap.targets create mode 100644 dalamud/Auracite/IStep.cs create mode 100644 dalamud/Auracite/PlaytimeStep.cs create mode 100644 dalamud/Auracite/Plugin.cs create mode 100644 dalamud/Auracite/StepWindow.cs create mode 100644 dalamud/Auracite/packages.lock.json create mode 100644 dalamud/global.json diff --git a/.gitignore b/.gitignore index db41fce..37fd804 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ /target .idea/ -config.json \ No newline at end of file +config.json +.vs/ +obj/ +bin/ +*.user \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 52532dd..3415466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,7 @@ dependencies = [ "scraper", "serde", "serde_json", + "touche", ] [[package]] @@ -123,6 +124,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -135,6 +142,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -230,6 +246,25 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cssparser" version = "0.31.2" @@ -264,6 +299,16 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "downloader" version = "0.2.8" @@ -452,6 +497,16 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getopts" version = "0.2.21" @@ -478,6 +533,30 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + [[package]] name = "heck" version = "0.5.0" @@ -504,6 +583,17 @@ dependencies = [ "syn", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.1.0" @@ -522,7 +612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.1.0", ] [[package]] @@ -533,7 +623,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http", + "http 1.1.0", "http-body", "pin-project-lite", ] @@ -544,6 +634,12 @@ version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.4.1" @@ -553,7 +649,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.1.0", "http-body", "httparse", "itoa", @@ -588,7 +684,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.1.0", "http-body", "hyper", "pin-project-lite", @@ -748,6 +844,16 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.36.4" @@ -1051,11 +1157,11 @@ version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", "futures-util", - "http", + "http 1.1.0", "http-body", "http-body-util", "hyper", @@ -1109,7 +1215,7 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64", + "base64 0.22.1", "rustls-pki-types", ] @@ -1251,6 +1357,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1390,6 +1507,15 @@ dependencies = [ "syn", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -1430,6 +1556,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "touche" +version = "0.0.10" +source = "git+https://github.com/redstrate/touche#8979a3367dcf79d605d818157ea187a52608fed9" +dependencies = [ + "headers", + "http 0.2.12", + "httparse", + "thiserror", + "threadpool", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -1461,6 +1599,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index 7bd33c3..ffc8ec4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,4 +23,9 @@ clap_derive = "4.5" downloader = "0.2" # Used to generate the HTML page to easily preview your exported data -minijinja = "2.0" \ No newline at end of file +minijinja = "2.0" + +# Used to communicate with the Dalamud plugin +# Needs my fork for allowing server shutdown +# TODO: upstream this or poke upstream to add this +touche = { git = "https://github.com/redstrate/touche" } \ No newline at end of file diff --git a/dalamud/Auracite.sln b/dalamud/Auracite.sln new file mode 100644 index 0000000..2369872 --- /dev/null +++ b/dalamud/Auracite.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29709.97 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auracite", "Auracite\Auracite.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64 + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64 + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64 + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64 + {4FEC9558-EB25-419F-B86E-51B8CFDA32B7}.Debug|x64.ActiveCfg = Debug|x64 + {4FEC9558-EB25-419F-B86E-51B8CFDA32B7}.Debug|x64.Build.0 = Debug|x64 + {4FEC9558-EB25-419F-B86E-51B8CFDA32B7}.Release|x64.ActiveCfg = Release|x64 + {4FEC9558-EB25-419F-B86E-51B8CFDA32B7}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF} + EndGlobalSection +EndGlobal diff --git a/dalamud/Auracite/Auracite.csproj b/dalamud/Auracite/Auracite.csproj new file mode 100644 index 0000000..43f8a48 --- /dev/null +++ b/dalamud/Auracite/Auracite.csproj @@ -0,0 +1,9 @@ + + + + + + 1.0.0.0 + NeoVARC + + diff --git a/dalamud/Auracite/Auracite.json b/dalamud/Auracite/Auracite.json new file mode 100644 index 0000000..2a06978 --- /dev/null +++ b/dalamud/Auracite/Auracite.json @@ -0,0 +1,8 @@ +{ + "Author": "redstrate", + "Name": "Auracite", + "Punchline": "Export your FFXIV character in portable, generic formats", + "Description": "Export your FFXIV character in portable, generic formats.", + "Tags": [], + "RepoUrl": "https://github.com/redstrate/Auracite" +} diff --git a/dalamud/Auracite/Dalamud.Plugin.Bootstrap.targets b/dalamud/Auracite/Dalamud.Plugin.Bootstrap.targets new file mode 100644 index 0000000..d9fe281 --- /dev/null +++ b/dalamud/Auracite/Dalamud.Plugin.Bootstrap.targets @@ -0,0 +1,12 @@ + + + + $(appdata)\XIVLauncher\addon\Hooks\dev\ + $(HOME)/.xlcore/dalamud/Hooks/dev/ + $(HOME)/.local/share/astra/dalamud/local/ + $(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/ + $(DALAMUD_HOME)/ + + + + diff --git a/dalamud/Auracite/IStep.cs b/dalamud/Auracite/IStep.cs new file mode 100644 index 0000000..79245a6 --- /dev/null +++ b/dalamud/Auracite/IStep.cs @@ -0,0 +1,13 @@ +using System; + +namespace Auracite; + +public interface IStep +{ + public event CompletedDelegate Completed; + + string StepName(); + string StepDescription(); + + delegate void CompletedDelegate(); +} \ No newline at end of file diff --git a/dalamud/Auracite/PlaytimeStep.cs b/dalamud/Auracite/PlaytimeStep.cs new file mode 100644 index 0000000..c910f46 --- /dev/null +++ b/dalamud/Auracite/PlaytimeStep.cs @@ -0,0 +1,41 @@ +using System; +using Dalamud.Game.Text; +using Dalamud.Game.Text.SeStringHandling; + +namespace Auracite; + +public class PlaytimeStep : IStep, IDisposable +{ + public PlaytimeStep() + { + Plugin.ChatGui.ChatMessage += OnChatMessage; + } + + public void Dispose() + { + Plugin.ChatGui.ChatMessage -= OnChatMessage; + } + + public event IStep.CompletedDelegate? Completed; + + public string StepName() + { + return "Playtime"; + } + + public string StepDescription() + { + return "Type /playtime into the chat window."; + } + + private void OnChatMessage(XivChatType type, int timestamp, ref SeString sender, ref SeString message, + ref bool ishandled) + { + var msgString = message.ToString(); + if (msgString.Contains("Total Play Time:") && type == XivChatType.SystemMessage) + { + Plugin.package.playtime = msgString.Split(": ")[1]; // TODO: lol + Completed?.Invoke(); + } + } +} \ No newline at end of file diff --git a/dalamud/Auracite/Plugin.cs b/dalamud/Auracite/Plugin.cs new file mode 100644 index 0000000..4c62bbf --- /dev/null +++ b/dalamud/Auracite/Plugin.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Dalamud.Game.Command; +using Dalamud.Interface.Windowing; +using Dalamud.IoC; +using Dalamud.Plugin; +using Dalamud.Plugin.Services; +using Newtonsoft.Json; + +namespace Auracite; + +public sealed class Plugin : IDalamudPlugin +{ + public static IStep? CurrentStep; + private readonly WindowSystem WindowSystem = new("Auracite"); + + private readonly List _steps = + [typeof(PlaytimeStep)]; + + private int _stepIndex; + + private readonly StepWindow StepWindow; + + public class Package + { + public string playtime; + } + + public static Package? package; + + public Plugin() + { + CommandManager.AddHandler("/auracite", new CommandInfo(OnAuraciteCommand) + { + HelpMessage = "Start the server." + }); + + StepWindow = new StepWindow(); + WindowSystem.AddWindow(StepWindow); + + PluginInterface.UiBuilder.Draw += WindowSystem.Draw; + } + + [PluginService] internal static IClientState ClientState { get; private set; } = null!; + + [PluginService] internal static IDalamudPluginInterface PluginInterface { get; private set; } = null!; + + [PluginService] internal static IChatGui ChatGui { get; private set; } = null!; + + [PluginService] internal static ICommandManager CommandManager { get; private set; } = null!; + + public void Dispose() + { + WindowSystem.RemoveAllWindows(); + } + + private void OnAuraciteCommand(string command, string arguments) + { + if (arguments == "begin" && CurrentStep == null) + { + _stepIndex = -1; + package = new Package(); + NextStep(); + StepWindow.IsOpen = true; + } + } + + private void NextStep() + { + _stepIndex++; + if (_stepIndex >= _steps.Count) + { + CurrentStep = null; + StepWindow.IsOpen = false; + SendPackage(); + return; + } + CurrentStep = (IStep)Activator.CreateInstance(_steps[_stepIndex])!; + CurrentStep.Completed += NextStep; + } + + private void SendPackage() + { + var client = new HttpClient(); + client.PostAsync("http://127.0.0.1:8000/package", new StringContent(JsonConvert.SerializeObject(package))); + package = null; + } +} \ No newline at end of file diff --git a/dalamud/Auracite/StepWindow.cs b/dalamud/Auracite/StepWindow.cs new file mode 100644 index 0000000..377cbc6 --- /dev/null +++ b/dalamud/Auracite/StepWindow.cs @@ -0,0 +1,28 @@ +using System; +using Dalamud.Interface.Windowing; +using ImGuiNET; + +namespace Auracite; + +public class StepWindow() + : Window("Step Window"), IDisposable +{ + public void Dispose() + { + } + + public override void Draw() + { + if (Plugin.CurrentStep != null) + { + ImGui.Text(Plugin.CurrentStep.StepName()); + ImGui.Text(Plugin.CurrentStep.StepDescription()); + + ImGui.TextDisabled("Step requires manual user action."); + } + else + { + ImGui.Text("Auracite is not running."); + } + } +} \ No newline at end of file diff --git a/dalamud/Auracite/packages.lock.json b/dalamud/Auracite/packages.lock.json new file mode 100644 index 0000000..19fcea9 --- /dev/null +++ b/dalamud/Auracite/packages.lock.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "dependencies": { + "net8.0-windows7.0": { + "DalamudPackager": { + "type": "Direct", + "requested": "[2.1.13, )", + "resolved": "2.1.13", + "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ==" + } + } + } +} \ No newline at end of file diff --git a/dalamud/global.json b/dalamud/global.json new file mode 100644 index 0000000..9e5e1fd --- /dev/null +++ b/dalamud/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/src/data.rs b/src/data.rs index dae535c..f854e53 100644 --- a/src/data.rs +++ b/src/data.rs @@ -17,6 +17,7 @@ pub struct CharacterData { pub nameday: String, pub guardian: String, pub currencies: Currencies, + pub playtime: String, #[serde(skip)] pub face_url: String, diff --git a/src/main.rs b/src/main.rs index ffed92d..984f768 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,13 @@ use crate::downloader::download; use crate::html::write_html; use crate::parser::{parse_lodestone, parse_search}; use clap::Parser; +use serde::Deserialize; +use std::convert::Infallible; use std::fs::{read, write}; use std::path::Path; +use std::sync::{Arc, Mutex}; +use touche::server::Service; +use touche::{Body, HttpBody, Request, Response, Server, StatusCode}; const LODESTONE_HOST: &str = "https://na.finalfantasyxiv.com"; @@ -17,6 +22,41 @@ const LODESTONE_HOST: &str = "https://na.finalfantasyxiv.com"; struct Args { #[arg(short, long, help = "The character's name.")] name: String, + + #[arg(short, long, help = "Whether to import more data from the Auracite Dalamud plugin.")] + dalamud: bool, +} + +#[derive(Default, Deserialize, Clone)] +struct Package { + playtime: String, +} + +#[derive(Clone)] +struct PackageService<'a> { + wants_stop: Arc>, // TODO: THIS IS TERRIBLE STOP STOP STOP + package: &'a Arc>, +} + +impl Service for PackageService<'_> { + type Body = &'static str; + type Error = Infallible; + + fn call(&self, req: Request) -> Result, Self::Error> { + *self.package.lock().unwrap() = serde_json::from_str(&String::from_utf8(req.into_body().into_bytes().unwrap()).unwrap()).unwrap(); + + *self.wants_stop.lock().unwrap() = true; + + Ok(Response::builder() + .status(StatusCode::OK) + .body("") + .unwrap()) + } + + // TODO: NO NO NO NO + fn wants_stop(&self) -> bool { + *self.wants_stop.lock().unwrap() + } } fn main() { @@ -29,7 +69,7 @@ fn main() { &format!("{LODESTONE_HOST}/lodestone/character/?q={}", args.name), search_page_path, ) - .expect("Failed to download the search page from the Lodestone."); + .expect("Failed to download the search page from the Lodestone."); let href = parse_search(&String::from_utf8(read(search_page_path).unwrap()).unwrap()); if href.is_empty() { @@ -40,7 +80,7 @@ fn main() { download(&format!("{LODESTONE_HOST}{}", href), char_page_path) .expect("Failed to download the character page from the Lodestone."); - let char_data = parse_lodestone(&String::from_utf8(read(char_page_path).unwrap()).unwrap()); + let mut char_data = parse_lodestone(&String::from_utf8(read(char_page_path).unwrap()).unwrap()); let character_folder = Path::new(&args.name); if !character_folder.exists() { @@ -52,13 +92,25 @@ fn main() { &char_data.portrait_url, &character_folder.join("portrait.jpg"), ) - .expect("Failed to download the character portrait image."); + .expect("Failed to download the character portrait image."); } if !char_data.face_url.is_empty() { download(&char_data.face_url, &character_folder.join("face.jpg")) .expect("Failed to download the character face image."); } + if args.dalamud { + println!("Now waiting for the Dalamud plugin. Type /auracite begin in chat."); + + let package = Arc::new(Mutex::new(Package::default())); + + Server::bind("0.0.0.0:8000").serve_single_thread(PackageService { wants_stop: Arc::new(Mutex::new(false)), package: &package }).unwrap(); + + let package = &*package.lock().unwrap(); + + char_data.playtime = package.playtime.parse().unwrap(); + } + let serialized = serde_json::to_string(&char_data).unwrap(); write(character_folder.join("character.json"), serialized) .expect("Failed to write the character JSON file."); @@ -76,5 +128,5 @@ fn main() { .into_string() .unwrap(), ) - .expect("Failed to write the character HTML file."); + .expect("Failed to write the character HTML file."); }