Compare commits

...

No commits in common. "main" and "5ca8dcc49033c59cfa28809bca5c1d2b71ab3768" have entirely different histories.

10 changed files with 132 additions and 163 deletions

View File

@ -1,46 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<Version>1.0.2</Version>
<TargetFramework>net5.0-windows</TargetFramework>
<Version>1.0.0</Version>
<Nullable>enable</Nullable>
<RootNamespace>PeepingTom.Ipc</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId>PeepingTom.Ipc</PackageId>
<Title>PeepingTom.Ipc</Title>
<Authors>lojewalo</Authors>
<RepositoryUrl>https://git.anna.lgbt/anna/PeepingTom</RepositoryUrl>
<Description>Types for IPC with the Dalamud plugin Peeping Tom.</Description>
<PackageLicenseExpression>EUPL-1.2</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev</DalamudLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
<DalamudLibPath>$(DALAMUD_HOME)</DalamudLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$(IsCI)' == 'true'">
<DalamudLibPath>$(HOME)/dalamud</DalamudLibPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)\Dalamud.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)\Lumina.Excel.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)\Newtonsoft.Json.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>

View File

@ -1,8 +1,8 @@
using System;
using System.Linq;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using Newtonsoft.Json;
namespace PeepingTom.Ipc {
@ -29,7 +29,7 @@ namespace PeepingTom.Ipc {
this.When = when;
}
public PlayerCharacter? GetPlayerCharacter(IObjectTable objectTable) {
public PlayerCharacter? GetPlayerCharacter(ObjectTable objectTable) {
return objectTable.FirstOrDefault(actor => actor.ObjectId == this.ObjectId && actor is PlayerCharacter) as PlayerCharacter;
}
}

View File

@ -37,9 +37,7 @@ namespace PeepingTom {
public bool PlaySoundOnTarget { get; set; }
public string? SoundPath { get; set; }
public float SoundVolume { get; set; } = 1f;
[Obsolete("use new", true)]
public int SoundDevice { get; set; } = -1;
public Guid SoundDeviceNew { get; set; } = Guid.Empty;
public float SoundCooldown { get; set; } = 10f;
public bool PlaySoundWhenClosed { get; set; }

View File

@ -8,12 +8,12 @@ using PeepingTom.Ipc.To;
namespace PeepingTom {
internal class IpcManager : IDisposable {
private Plugin Plugin { get; }
private PeepingTomPlugin Plugin { get; }
private ICallGateProvider<IFromMessage, object> Provider { get; }
private ICallGateSubscriber<IToMessage, object> Subscriber { get; }
internal IpcManager(Plugin plugin) {
internal IpcManager(PeepingTomPlugin plugin) {
this.Plugin = plugin;
this.Provider = this.Plugin.Interface.GetIpcProvider<IFromMessage, object>(IpcInfo.FromRegistrationName);

View File

@ -1,26 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7-windows</TargetFramework>
<TargetFramework>net5-windows</TargetFramework>
<RootNamespace>PeepingTom</RootNamespace>
<Version>1.7.16</Version>
<Version>1.7.7</Version>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<AssemblyName>PeepingTom</AssemblyName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup>
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev</DalamudLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
<DalamudLibPath>$(DALAMUD_HOME)</DalamudLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$(IsCI)' == 'true'">
<DalamudLibPath>$(HOME)/dalamud</DalamudLibPath>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\target.wav"/>
<EmbeddedResource Update="Resources\Language.resx">
@ -30,33 +20,32 @@
</ItemGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)\Dalamud.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)\ImGui.NET.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)\Lumina.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)\Lumina.Excel.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)\Newtonsoft.Json.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.12"/>
<PackageReference Include="Fody" Version="6.8.0" PrivateAssets="all"/>
<PackageReference Include="NAudio.Core" Version="2.2.1"/>
<PackageReference Include="NAudio.Wasapi" Version="2.2.1"/>
<PackageReference Include="Resourcer.Fody" Version="1.8.1" PrivateAssets="all"/>
<PackageReference Include="XivCommon" Version="9.0.0"/>
<PackageReference Include="DalamudPackager" Version="2.1.2"/>
<PackageReference Include="Fody" Version="6.5.2" PrivateAssets="all"/>
<PackageReference Include="NAudio" Version="2.0.1"/>
<PackageReference Include="Resourcer.Fody" Version="1.8.0" PrivateAssets="all"/>
<PackageReference Include="XivCommon" Version="3.0.1"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Peeping Tom.Ipc\Peeping Tom.Ipc.csproj"/>
@ -68,4 +57,7 @@
<DependentUpon>Language.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Content Include="..\icon.png" Link="images/icon.png" CopyToOutputDirectory="PreserveNewest" Visible="false"/>
</ItemGroup>
</Project>

View File

@ -1,5 +1,5 @@
author: Anna
author: ascclemens
name: Peeping Tom
punchline: Shows who is currently or was previously targeting you.
description: Shows who is currently or was previously targeting you.
repo_url: https://git.anna.lgbt/anna/PeepingTom
repo_url: https://sr.ht/~jkcclemens/PeepingTom

View File

@ -1,54 +1,58 @@
using Dalamud.Game.Command;
using Dalamud.Plugin;
using System;
using System.Collections.Generic;
using System.Globalization;
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Gui;
using Dalamud.Game.Gui.Toast;
using Dalamud.IoC;
using Dalamud.Plugin.Services;
using Dalamud.Logging;
using Lumina.Excel.GeneratedSheets;
using PeepingTom.Resources;
using XivCommon;
using Condition = Dalamud.Game.ClientState.Conditions.Condition;
namespace PeepingTom {
// ReSharper disable once ClassNeverInstantiated.Global
public class Plugin : IDalamudPlugin {
internal static string Name => "Peeping Tom";
[PluginService]
internal static IPluginLog Log { get; private set; } = null!;
public class PeepingTomPlugin : IDalamudPlugin {
public string Name => "Peeping Tom";
[PluginService]
internal DalamudPluginInterface Interface { get; init; } = null!;
[PluginService]
internal IChatGui ChatGui { get; init; } = null!;
internal ChatGui ChatGui { get; init; } = null!;
[PluginService]
internal IClientState ClientState { get; init; } = null!;
internal ClientState ClientState { get; init; } = null!;
[PluginService]
private ICommandManager CommandManager { get; init; } = null!;
private CommandManager CommandManager { get; init; } = null!;
[PluginService]
internal ICondition Condition { get; init; } = null!;
internal Condition Condition { get; init; } = null!;
[PluginService]
internal IDataManager DataManager { get; init; } = null!;
internal DataManager DataManager { get; init; } = null!;
[PluginService]
internal IFramework Framework { get; init; } = null!;
internal Framework Framework { get; init; } = null!;
[PluginService]
internal IGameGui GameGui { get; init; } = null!;
internal GameGui GameGui { get; init; } = null!;
[PluginService]
internal IObjectTable ObjectTable { get; init; } = null!;
internal ObjectTable ObjectTable { get; init; } = null!;
[PluginService]
internal ITargetManager TargetManager { get; init; } = null!;
internal TargetManager TargetManager { get; init; } = null!;
[PluginService]
internal IToastGui ToastGui { get; init; } = null!;
internal ToastGui ToastGui { get; init; } = null!;
internal Configuration Config { get; }
internal PluginUi Ui { get; }
@ -58,8 +62,8 @@ namespace PeepingTom {
internal bool InPvp { get; private set; }
public Plugin() {
this.Common = new XivCommonBase(this.Interface);
public PeepingTomPlugin() {
this.Common = new XivCommonBase();
this.Config = this.Interface.GetPluginConfig() as Configuration ?? new Configuration();
this.Config.Initialize(this.Interface);
this.Watcher = new TargetWatcher(this);
@ -106,12 +110,12 @@ namespace PeepingTom {
Language.Culture = new CultureInfo(langCode);
}
private void OnTerritoryChange(ushort e) {
private void OnTerritoryChange(object? sender, ushort e) {
try {
var territory = this.DataManager.GetExcelSheet<TerritoryType>()!.GetRow(e);
this.InPvp = territory?.IsPvpZone == true;
} catch (KeyNotFoundException) {
Log.Warning("Could not get territory for current zone");
PluginLog.Warning("Could not get territory for current zone");
}
}
@ -123,7 +127,7 @@ namespace PeepingTom {
}
}
private void OnLogin() {
private void OnLogin(object? sender, EventArgs args) {
if (!this.Config.OpenOnLogin) {
return;
}
@ -131,7 +135,7 @@ namespace PeepingTom {
this.Ui.WantsOpen = true;
}
private void OnLogout() {
private void OnLogout(object? sender, EventArgs args) {
this.Ui.WantsOpen = false;
this.Watcher.ClearPrevious();
}

View File

@ -11,13 +11,13 @@ using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.Utility;
using Dalamud.Interface;
using PeepingTom.Ipc;
using PeepingTom.Resources;
namespace PeepingTom {
internal class PluginUi : IDisposable {
private Plugin Plugin { get; }
private PeepingTomPlugin Plugin { get; }
private uint? PreviousFocus { get; set; } = new();
@ -37,7 +37,7 @@ namespace PeepingTom {
set => this._settingsOpen = value;
}
public PluginUi(Plugin plugin) {
public PluginUi(PeepingTomPlugin plugin) {
this.Plugin = plugin;
}
@ -124,7 +124,7 @@ namespace PeepingTom {
private void ShowSettings() {
ImGui.SetNextWindowSize(new Vector2(700, 250));
var windowTitle = string.Format(Language.SettingsTitle, Plugin.Name);
var windowTitle = string.Format(Language.SettingsTitle, this.Plugin.Name);
if (!ImGui.Begin($"{windowTitle}###ptom-settings", ref this._settingsOpen, ImGuiWindowFlags.NoResize)) {
ImGui.End();
return;
@ -241,18 +241,32 @@ namespace PeepingTom {
this.Plugin.Config.Save();
}
var devices = DirectSoundOut.Devices.ToList();
var soundDevice = devices.FirstOrDefault(d => d.Guid == this.Plugin.Config.SoundDeviceNew);
var name = soundDevice != null ? soundDevice.Description : Language.SettingsSoundInvalidDevice;
var soundDevice = this.Plugin.Config.SoundDevice;
string name;
if (soundDevice == -1) {
name = Language.SettingsSoundDefaultDevice;
} else if (soundDevice > -1 && soundDevice < WaveOut.DeviceCount) {
var caps = WaveOut.GetCapabilities(soundDevice);
name = caps.ProductName;
} else {
name = Language.SettingsSoundInvalidDevice;
}
if (ImGui.BeginCombo($"{Language.SettingsSoundOutputDevice}###sound-output-device-combo", name)) {
for (var deviceNum = 0; deviceNum < devices.Count; deviceNum++) {
var info = devices[deviceNum];
if (!ImGui.Selectable($"{info.Description}##{deviceNum}")) {
if (ImGui.Selectable(Language.SettingsSoundDefaultDevice)) {
this.Plugin.Config.SoundDevice = -1;
this.Plugin.Config.Save();
}
ImGui.Separator();
for (var deviceNum = 0; deviceNum < WaveOut.DeviceCount; deviceNum++) {
var caps = WaveOut.GetCapabilities(deviceNum);
if (!ImGui.Selectable(caps.ProductName)) {
continue;
}
this.Plugin.Config.SoundDeviceNew = info.Guid;
this.Plugin.Config.SoundDevice = deviceNum;
this.Plugin.Config.Save();
}
@ -356,6 +370,41 @@ namespace PeepingTom {
ImGui.EndTabItem();
}
#if DEBUG
if (ImGui.BeginTabItem("Debug")) {
if (ImGui.Button("Log targeting you")) {
var player = this.Plugin.Interface.ClientState.LocalPlayer;
if (player != null) {
// loop over all players looking at the current player
var actors = this.Plugin.Interface.ClientState.Actors
.Where(actor => actor.TargetActorID == player.ActorId && actor is PlayerCharacter)
.Cast<PlayerCharacter>();
foreach (var actor in actors) {
var payload = new PlayerPayload(this.Plugin.Interface.Data, actor.Name.TextValue, actor.HomeWorld.Id);
Payload[] payloads = {payload};
this.Plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
Message = new SeString(payloads),
});
}
}
}
if (ImGui.Button("Log your target")) {
var target = this.GetCurrentTarget();
if (target != null) {
var payload = new PlayerPayload(this.Plugin.Interface.Data, target.Name.TextValue, target.HomeWorld.Id);
Payload[] payloads = {payload};
this.Plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
Message = new SeString(payloads),
});
}
}
ImGui.EndTabItem();
}
#endif
ImGui.EndTabBar();
}
@ -392,7 +441,7 @@ namespace PeepingTom {
}
ImGui.SetNextWindowSize(new Vector2(290, 195), ImGuiCond.FirstUseEver);
if (!ImGui.Begin(Plugin.Name, ref this._wantsOpen, flags)) {
if (!ImGui.Begin(this.Plugin.Name, ref this._wantsOpen, flags)) {
ImGui.End();
return;
}
@ -479,8 +528,7 @@ namespace PeepingTom {
var time = DateTime.UtcNow - targeter.When >= TimeSpan.FromDays(1)
? targeter.When.ToLocalTime().ToString("dd/MM")
: targeter.When.ToLocalTime().ToString("t");
var windowWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
ImGui.SameLine(windowWidth - ImGui.CalcTextSize(time).X);
ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - ImGui.CalcTextSize(time).X);
if (flags.HasFlag(ImGuiSelectableFlags.Disabled)) {
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]);
@ -522,7 +570,7 @@ namespace PeepingTom {
} else {
var payload = new PlayerPayload(targeter.Name.TextValue, targeter.HomeWorldId);
Payload[] payloads = { payload };
this.Plugin.ChatGui.Print(new XivChatEntry {
this.Plugin.ChatGui.PrintChat(new XivChatEntry {
Message = new SeString(payloads),
});
}

View File

@ -6,16 +6,17 @@ using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Dalamud.Game;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services;
using Dalamud.Logging;
using PeepingTom.Ipc;
using PeepingTom.Resources;
namespace PeepingTom {
internal class TargetWatcher : IDisposable {
private Plugin Plugin { get; }
private PeepingTomPlugin Plugin { get; }
private Stopwatch UpdateWatch { get; } = new();
private Stopwatch? SoundWatch { get; set; }
@ -29,7 +30,7 @@ namespace PeepingTom {
public IReadOnlyCollection<Targeter> PreviousTargeters => this.Previous;
public TargetWatcher(Plugin plugin) {
public TargetWatcher(PeepingTomPlugin plugin) {
this.Plugin = plugin;
this.UpdateWatch.Start();
@ -44,7 +45,7 @@ namespace PeepingTom {
this.Previous.Clear();
}
private void OnFrameworkUpdate(IFramework framework1) {
private void OnFrameworkUpdate(Framework framework) {
if (this.Plugin.InPvp) {
return;
}
@ -67,7 +68,7 @@ namespace PeepingTom {
try {
this.Plugin.IpcManager.SendNewTargeter(newTargeter);
} catch (Exception ex) {
Plugin.Log.Error(ex, "Failed to send IPC message");
PluginLog.LogError(ex, "Failed to send IPC message");
}
}
@ -75,7 +76,7 @@ namespace PeepingTom {
try {
this.Plugin.IpcManager.SendStoppedTargeting(stopped);
} catch (Exception ex) {
Plugin.Log.Error(ex, "Failed to send IPC message");
PluginLog.LogError(ex, "Failed to send IPC message");
}
}
@ -159,9 +160,9 @@ namespace PeepingTom {
}
private void PlaySound() {
var soundDevice = DirectSoundOut.Devices.FirstOrDefault(d => d.Guid == this.Plugin.Config.SoundDeviceNew);
if (soundDevice == null) {
return;
var soundDevice = this.Plugin.Config.SoundDevice;
if (soundDevice < -1 || soundDevice > WaveOut.DeviceCount) {
soundDevice = -1;
}
new Thread(() => {
@ -170,7 +171,7 @@ namespace PeepingTom {
if (this.Plugin.Config.SoundPath == null) {
reader = new WaveFileReader(Resource.AsStream("Resources/target.wav"));
} else {
reader = new MediaFoundationReader(this.Plugin.Config.SoundPath);
reader = new AudioFileReader(this.Plugin.Config.SoundPath);
}
} catch (Exception e) {
var error = string.Format(Language.SoundChatError, e.Message);
@ -184,8 +185,9 @@ namespace PeepingTom {
};
using (reader) {
using var output = new DirectSoundOut(soundDevice.Guid);
using var output = new WaveOutEvent {
DeviceNumber = soundDevice,
};
try {
output.Init(channel);
output.Play();
@ -194,15 +196,15 @@ namespace PeepingTom {
Thread.Sleep(500);
}
} catch (Exception ex) {
Plugin.Log.Error(ex, "Exception playing sound");
PluginLog.LogError(ex, "Exception playing sound");
}
}
}).Start();
}
private void SendError(string message) {
this.Plugin.ChatGui.Print(new XivChatEntry {
Message = $"[{Plugin.Name}] {message}",
this.Plugin.ChatGui.PrintChat(new XivChatEntry {
Message = $"[{this.Plugin.Name}] {message}",
Type = XivChatType.ErrorMessage,
});
}

View File

@ -1,52 +0,0 @@
{
"version": 1,
"dependencies": {
"net7.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[2.1.12, )",
"resolved": "2.1.12",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
},
"Fody": {
"type": "Direct",
"requested": "[6.8.0, )",
"resolved": "6.8.0",
"contentHash": "hfZ/f8Mezt8aTkgv9nsvFdYoQ809/AqwsJlOGOPYIfBcG2aAIG3v3ex9d8ZqQuFYyMoucjRg4eKy3VleeGodKQ=="
},
"NAudio.Core": {
"type": "Direct",
"requested": "[2.2.1, )",
"resolved": "2.2.1",
"contentHash": "GgkdP6K/7FqXFo7uHvoqGZTJvW4z8g2IffhOO4JHaLzKCdDOUEzVKtveoZkCuUX8eV2HAINqi7VFqlFndrnz/g=="
},
"NAudio.Wasapi": {
"type": "Direct",
"requested": "[2.2.1, )",
"resolved": "2.2.1",
"contentHash": "lFfXoqacZZe0WqNChJgGYI+XV/n/61LzPHT3C1CJp4khoxeo2sziyX5wzNYWeCMNbsWxFvT3b3iXeY1UYjBhZw==",
"dependencies": {
"NAudio.Core": "2.2.1"
}
},
"Resourcer.Fody": {
"type": "Direct",
"requested": "[1.8.1, )",
"resolved": "1.8.1",
"contentHash": "FPeK4jKyyX5+mIjTnHNReGZk2/2xDhmu44UsBI5w9WEhbr4oTMmht3rnBr46A+GCGepC4+2N41K4vExDYiGNVQ==",
"dependencies": {
"Fody": "6.6.4"
}
},
"XivCommon": {
"type": "Direct",
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "avaBp3FmSCi/PiQhntCeBDYOHejdyTWmFtz4pRBVQQ8vHkmRx+YTk1la9dkYBMlXxRXKckEdH1iI1Fu61JlE7w=="
},
"PeepingTom.Ipc": {
"type": "Project"
}
}
}
}