remove whackerlinklib submodule

pull/1/head
firealarmss 11 months ago
parent e3728f12ff
commit d69e74237a

3
.gitmodules vendored

@ -1,6 +1,3 @@
[submodule "WhackerLinkLib"]
path = WhackerLinkLib
url = https://github.com/whackerlink/WhackerLinkLib
[submodule "fnecore"] [submodule "fnecore"]
path = fnecore path = fnecore
url = https://github.com/dvmproject/fnecore url = https://github.com/dvmproject/fnecore

@ -3,9 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188 VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WhackerLinkLib", "WhackerLinkLib\WhackerLinkLib.csproj", "{5918329A-6374-40E2-874D-445360C89676}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DVMConsole", "DVMConsole\DVMConsole.csproj", "{710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhackerLinkConsoleV2", "WhackerLinkConsoleV2\WhackerLinkConsoleV2.csproj", "{710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B5A7CF60-CCDE-4B2B-85C1-86AE3A19FB31}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B5A7CF60-CCDE-4B2B-85C1-86AE3A19FB31}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
@ -30,30 +28,6 @@ Global
WIN32|x86 = WIN32|x86 WIN32|x86 = WIN32|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5918329A-6374-40E2-874D-445360C89676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5918329A-6374-40E2-874D-445360C89676}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5918329A-6374-40E2-874D-445360C89676}.Debug|x64.ActiveCfg = AmbeVocode|x64
{5918329A-6374-40E2-874D-445360C89676}.Debug|x64.Build.0 = AmbeVocode|x64
{5918329A-6374-40E2-874D-445360C89676}.Debug|x86.ActiveCfg = Debug|x86
{5918329A-6374-40E2-874D-445360C89676}.Debug|x86.Build.0 = Debug|x86
{5918329A-6374-40E2-874D-445360C89676}.NoVocode|Any CPU.ActiveCfg = NoVocode|Any CPU
{5918329A-6374-40E2-874D-445360C89676}.NoVocode|Any CPU.Build.0 = NoVocode|Any CPU
{5918329A-6374-40E2-874D-445360C89676}.NoVocode|x64.ActiveCfg = NoVocode|x64
{5918329A-6374-40E2-874D-445360C89676}.NoVocode|x64.Build.0 = NoVocode|x64
{5918329A-6374-40E2-874D-445360C89676}.NoVocode|x86.ActiveCfg = NoVocode|x86
{5918329A-6374-40E2-874D-445360C89676}.NoVocode|x86.Build.0 = NoVocode|x86
{5918329A-6374-40E2-874D-445360C89676}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5918329A-6374-40E2-874D-445360C89676}.Release|Any CPU.Build.0 = Release|Any CPU
{5918329A-6374-40E2-874D-445360C89676}.Release|x64.ActiveCfg = Release|x64
{5918329A-6374-40E2-874D-445360C89676}.Release|x64.Build.0 = Release|x64
{5918329A-6374-40E2-874D-445360C89676}.Release|x86.ActiveCfg = Release|x86
{5918329A-6374-40E2-874D-445360C89676}.Release|x86.Build.0 = Release|x86
{5918329A-6374-40E2-874D-445360C89676}.WIN32|Any CPU.ActiveCfg = WIN32|Any CPU
{5918329A-6374-40E2-874D-445360C89676}.WIN32|Any CPU.Build.0 = WIN32|Any CPU
{5918329A-6374-40E2-874D-445360C89676}.WIN32|x64.ActiveCfg = WIN32|x64
{5918329A-6374-40E2-874D-445360C89676}.WIN32|x64.Build.0 = WIN32|x64
{5918329A-6374-40E2-874D-445360C89676}.WIN32|x86.ActiveCfg = WIN32|x86
{5918329A-6374-40E2-874D-445360C89676}.WIN32|x86.Build.0 = WIN32|x86
{710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Debug|Any CPU.Build.0 = Debug|Any CPU {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Debug|x64.ActiveCfg = Debug|x64 {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Debug|x64.ActiveCfg = Debug|x64

@ -1,4 +1,4 @@
<UserControl x:Class="WhackerLinkConsoleV2.Controls.AlertTone" <UserControl x:Class="DVMConsole.Controls.AlertTone"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="55" Width="105" Height="55" Width="105"

@ -1,5 +1,5 @@
/* /*
* WhackerLink - WhackerLinkConsoleV2 * WhackerLink - DVMConsole
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -24,7 +24,7 @@ using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
namespace WhackerLinkConsoleV2.Controls namespace DVMConsole.Controls
{ {
public partial class AlertTone : UserControl public partial class AlertTone : UserControl
{ {

@ -1,18 +1,11 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* Copyright (C) 2025 Caleb, K4PHP * Copyright (C) 2025 Caleb, K4PHP
* *
@ -22,12 +15,11 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using WhackerLinkLib.Models;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using System.Diagnostics; using System.Diagnostics;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public static class AliasTools public static class AliasTools
{ {
@ -55,4 +47,10 @@ namespace WhackerLinkConsoleV2
return match?.Alias ?? string.Empty; return match?.Alias ?? string.Empty;
} }
} }
public class RadioAlias
{
public string Alias { get; set; }
public int Rid { get; set; }
}
} }

@ -1,12 +1,18 @@
using System; // SPDX-License-Identifier: AGPL-3.0-only
using System.Collections.Generic; /**
using System.Linq; * Digital Voice Modem - Audio Bridge
using System.Runtime.InteropServices; * AGPLv3 Open Source. Use is subject to license terms.
using System.Text; * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
using System.Threading.Tasks; *
* @package DVM / Audio Bridge
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
*
*/
#if WIN32 #if WIN32
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// Implements P/Invoke to callback into external AMBE encoder/decoder library. /// Implements P/Invoke to callback into external AMBE encoder/decoder library.

@ -2,7 +2,7 @@
using System.Data; using System.Data;
using System.Windows; using System.Windows;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// Interaction logic for App.xaml /// Interaction logic for App.xaml

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

@ -0,0 +1,92 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DVM Console
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
namespace DVMConsole
{
/// <summary>
/// Helper to convert audio between different chunk sizes
/// </summary>
public static class AudioConverter
{
public const int OriginalPcmLength = 1600;
public const int ExpectedPcmLength = 320;
/// <summary>
/// Helper to go from a big chunk size to smaller
/// </summary>
/// <param name="audioData"></param>
/// <returns></returns>
public static List<byte[]> SplitToChunks(byte[] audioData, int OgLength = OriginalPcmLength, int ExepcetedLength = ExpectedPcmLength)
{
List<byte[]> chunks = new List<byte[]>();
if (audioData.Length != OgLength)
{
Console.WriteLine($"Invalid PCM length: {audioData.Length}, expected: {OgLength}");
return chunks;
}
for (int offset = 0; offset < OgLength; offset += ExepcetedLength)
{
byte[] chunk = new byte[ExpectedPcmLength];
Buffer.BlockCopy(audioData, offset, chunk, 0, ExepcetedLength);
chunks.Add(chunk);
}
return chunks;
}
/// <summary>
/// Helper to go from small chunks to a big chunk
/// </summary>
/// <param name="chunks"></param>
/// <returns></returns>
public static byte[] CombineChunks(List<byte[]> chunks, int OgLength = OriginalPcmLength, int ExepcetedLength = ExpectedPcmLength)
{
if (chunks.Count * ExepcetedLength != OgLength)
{
Console.WriteLine($"Invalid number of chunks: {chunks.Count}, expected total length: {OgLength}");
return null;
}
byte[] combined = new byte[OgLength];
int offset = 0;
foreach (var chunk in chunks)
{
Buffer.BlockCopy(chunk, 0, combined, offset, ExepcetedLength);
offset += ExepcetedLength;
}
return combined;
}
/// <summary>
/// From https://github.com/W3AXL/rc2-dvm/blob/main/rc2-dvm/Audio.cs
/// </summary>
/// <param name="pcm16"></param>
/// <returns></returns>
public static float[] PcmToFloat(short[] pcm16)
{
float[] floats = new float[pcm16.Length];
for (int i = 0; i < pcm16.Length; i++)
{
float v = (float)pcm16[i] / (float)short.MaxValue;
if (v > 1) { v = 1; }
if (v < -1) { v = -1; }
floats[i] = v;
}
return floats;
}
}
}

@ -1,18 +1,11 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* Copyright (C) 2025 Caleb, K4PHP * Copyright (C) 2025 Caleb, K4PHP
* *
@ -20,12 +13,11 @@
using NAudio.Wave; using NAudio.Wave;
using NAudio.Wave.SampleProviders; using NAudio.Wave.SampleProviders;
using System.Collections.Generic;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// Class for managing /// Class for managing audio streams
/// </summary> /// </summary>
public class AudioManager public class AudioManager
{ {

@ -1,4 +1,4 @@
<Window x:Class="WhackerLinkConsoleV2.AudioSettingsWindow" <Window x:Class="DVMConsole.AudioSettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Audio Settings" Height="400" Width="450" Title="Audio Settings" Height="400" Width="450"

@ -1,20 +1,13 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * Copyright (C) 2025 Caleb, K4PHP
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024-2025 Caleb, K4PHP
* *
*/ */
@ -23,9 +16,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NAudio.Wave; using NAudio.Wave;
using System.Windows.Controls; using System.Windows.Controls;
using WhackerLinkLib.Models.Radio;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public partial class AudioSettingsWindow : Window public partial class AudioSettingsWindow : Window
{ {

@ -1,9 +1,9 @@
<Window x:Class="WhackerLinkConsoleV2.CallHistoryWindow" <Window x:Class="DVMConsole.CallHistoryWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WhackerLinkConsoleV2" xmlns:local="clr-namespace:DVMConsole"
mc:Ignorable="d" mc:Ignorable="d"
Title="Call History" Height="450" Width="351"> Title="Call History" Height="450" Width="351">
<Grid> <Grid>

@ -1,11 +1,21 @@
using System; // SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DVM Console
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public partial class CallHistoryWindow : Window public partial class CallHistoryWindow : Window
{ {

@ -1,4 +1,4 @@
<UserControl x:Class="WhackerLinkConsoleV2.Controls.ChannelBox" <UserControl x:Class="DVMConsole.Controls.ChannelBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="220" Height="100" Background="#FF0B004B"> Width="220" Height="100" Background="#FF0B004B">
@ -88,7 +88,7 @@
</Style> </Style>
</Button.Resources> </Button.Resources>
<Image Source="pack://application:,,,/WhackerLinkConsoleV2;component/Assets/pageselect.png" Width="39" Height="39" Margin="1.5,2,0,0"/> <Image Source="/Assets/pageselect.png" Width="39" Height="39" Margin="1.5,2,0,0"/>
</Button> </Button>
<Button Width="40" Height="40" x:Name="ChannelMarkerBtn" Click="ChannelMarkerBtn_Click" BorderThickness="0,0,0,0" Margin="5,0,0,0"> <Button Width="40" Height="40" x:Name="ChannelMarkerBtn" Click="ChannelMarkerBtn_Click" BorderThickness="0,0,0,0" Margin="5,0,0,0">
<Button.Resources> <Button.Resources>
@ -97,7 +97,7 @@
</Style> </Style>
</Button.Resources> </Button.Resources>
<Image Source="pack://application:,,,/WhackerLinkConsoleV2;component/Assets/channelmarker.png" Width="36" Height="40"/> <Image Source="/Assets/channelmarker.png" Width="36" Height="40"/>
</Button> </Button>
</StackPanel> </StackPanel>
<Button x:Name="PttButton" Click="PTTButton_Click" HorizontalAlignment="Left" Width="42" Margin="41,1,0,4" Grid.ColumnSpan="2" BorderThickness="0,0,0,0" BorderBrush="#FFC1C1C1" UseLayoutRounding="False" Grid.RowSpan="2" Background="#FFEEA400"> <Button x:Name="PttButton" Click="PTTButton_Click" HorizontalAlignment="Left" Width="42" Margin="41,1,0,4" Grid.ColumnSpan="2" BorderThickness="0,0,0,0" BorderBrush="#FFC1C1C1" UseLayoutRounding="False" Grid.RowSpan="2" Background="#FFEEA400">

@ -1,20 +1,13 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * Copyright (C) 2025 Caleb, K4PHP
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024-2025 Caleb, K4PHP
* *
*/ */
@ -26,7 +19,7 @@ using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using fnecore.P25; using fnecore.P25;
namespace WhackerLinkConsoleV2.Controls namespace DVMConsole.Controls
{ {
public partial class ChannelBox : UserControl, INotifyPropertyChanged public partial class ChannelBox : UserControl, INotifyPropertyChanged
{ {

@ -0,0 +1,21 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DVM Console
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
namespace DVMConsole
{
public class ChannelPosition
{
public double X { get; set; }
public double Y { get; set; }
}
}

@ -0,0 +1,136 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DVM Console
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2024-2025 Caleb, K4PHP
*
*/
using System.Security.Policy;
namespace DVMConsole
{
/// <summary>
/// Codeplug object used project wide
/// </summary>
public class Codeplug
{
public List<System> Systems { get; set; }
public List<Zone> Zones { get; set; }
/// <summary>
///
/// </summary>
public class System
{
public string Name { get; set; }
public string Address { get; set; }
public uint PeerId { get; set; }
public int Port { get; set; }
public string Rid { get; set; }
public string AuthKey { get; set; }
public string AliasPath { get; set; } = "./alias.yml";
public List<RadioAlias> RidAlias { get; set; } = null;
public override string ToString()
{
return Name;
}
}
/// <summary>
///
/// </summary>
public class Zone
{
public string Name { get; set; }
public List<Channel> Channels { get; set; }
}
/// <summary>
///
/// </summary>
public class Channel
{
public string Name { get; set; }
public string System { get; set; }
public string Tgid { get; set; }
public string EncryptionKey { get; set; }
public string AlgoId { get; set; } = "0x80";
public string KeyId { get; set; }
public ushort GetKeyId()
{
return Convert.ToUInt16(KeyId, 16);
}
public byte GetAlgoId()
{
return Convert.ToByte(AlgoId, 16);
}
public byte[] GetEncryptionKey()
{
if (EncryptionKey == null)
return [];
return EncryptionKey
.Split(',')
.Select(s => Convert.ToByte(s.Trim(), 16))
.ToArray();
}
}
/// <summary>
/// Helper to return a system by looking up a <see cref="Channel"/>
/// </summary>
/// <param name="channel"></param>
/// <returns></returns>
public System GetSystemForChannel(Channel channel)
{
return Systems.FirstOrDefault(s => s.Name == channel.System);
}
/// <summary>
/// Helper to return a system by looking up a channel name
/// </summary>
/// <param name="channelName"></param>
/// <returns></returns>
public System GetSystemForChannel(string channelName)
{
foreach (var zone in Zones)
{
var channel = zone.Channels.FirstOrDefault(c => c.Name == channelName);
if (channel != null)
{
return Systems.FirstOrDefault(s => s.Name == channel.System);
}
}
return null;
}
/// <summary>
/// Helper to return a <see cref="Channel"/> by channel name
/// </summary>
/// <param name="channelName"></param>
/// <returns></returns>
public Channel GetChannelByName(string channelName)
{
foreach (var zone in Zones)
{
var channel = zone.Channels.FirstOrDefault(c => c.Name == channelName);
if (channel != null)
{
return channel;
}
}
return null;
}
}
}

@ -0,0 +1,29 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DVM Console
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
using System.Runtime.InteropServices;
namespace DVMConsole
{
public static class ConsoleNative
{
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
public static void ShowConsole()
{
AllocConsole();
Console.WriteLine("Console attached.");
}
}
}

@ -21,6 +21,7 @@
<None Remove="Assets\config.png" /> <None Remove="Assets\config.png" />
<None Remove="Assets\config2.png" /> <None Remove="Assets\config2.png" />
<None Remove="Assets\connection.png" /> <None Remove="Assets\connection.png" />
<None Remove="Assets\DvmLogo.png" />
<None Remove="Assets\instantptt.png" /> <None Remove="Assets\instantptt.png" />
<None Remove="Assets\page.png" /> <None Remove="Assets\page.png" />
<None Remove="Assets\pageselect.png" /> <None Remove="Assets\pageselect.png" />
@ -35,42 +36,67 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="Assets\alerttone.png" /> <Content Include="Assets\alerttone.png">
<Resource Include="Assets\alerttone2.png" /> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Resource Include="Assets\audio.png" /> </Content>
<Content Include="Assets\alerttone2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\audio.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="Assets\config.png" /> <Content Include="Assets\config.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NAudio" Version="2.2.1" /> <PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NWaves" Version="0.9.6" /> <PackageReference Include="NWaves" Version="0.9.6" />
<PackageReference Include="YamlDotNet" Version="16.2.0" /> <PackageReference Include="YamlDotNet" Version="16.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\fnecore\fnecore.csproj" /> <ProjectReference Include="..\fnecore\fnecore.csproj" />
<ProjectReference Include="..\WhackerLinkLib\WhackerLinkLib.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="Assets\channelmarker.png"> <Content Include="Assets\channelmarker.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource> </Content>
<Resource Include="Assets\clearemerg.png" /> <Content Include="Assets\clearemerg.png">
<Resource Include="Assets\config2.png" /> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Resource Include="Assets\connection.png" /> </Content>
<Resource Include="Assets\instantptt.png" /> <Content Include="Assets\config2.png">
<Resource Include="Assets\page.png" /> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Resource Include="Assets\pageselect.png" /> </Content>
<Resource Include="Assets\pttselect.png" /> <Content Include="Assets\connection.png">
<Resource Include="Assets\whackerlink-logo.png" /> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Resource Include="Assets\WhackerLinkLogoV2.png" /> </Content>
<Resource Include="Assets\WhackerLinkLogoV4.png" /> <Content Include="Assets\DvmLogo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\instantptt.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\page.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\pageselect.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\pttselect.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\WhackerLinkLogoV4.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -89,6 +115,9 @@
<None Update="Audio\hold.wav"> <None Update="Audio\hold.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="codeplugs\codeplug.yml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -1,4 +1,4 @@
<Window x:Class="WhackerLinkConsoleV2.DigitalPageWindow" <Window x:Class="DVMConsole.DigitalPageWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

@ -1,27 +1,19 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * Copyright (C) 2025 Caleb, K4PHP
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024 Caleb, K4PHP
* *
*/ */
using System.Windows; using System.Windows;
using WhackerLinkLib.Models.Radio;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// Interaction logic for DigitalPageWindow.xaml /// Interaction logic for DigitalPageWindow.xaml

@ -1,20 +1,13 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * Copyright (C) 2025 Caleb, K4PHP
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024 Caleb, K4PHP
* *
*/ */
@ -23,7 +16,7 @@ using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading; using System.Windows.Threading;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public class FlashingBackgroundManager public class FlashingBackgroundManager
{ {

@ -1,19 +1,22 @@
using Microsoft.VisualBasic; // SPDX-License-Identifier: AGPL-3.0-only
using Serilog; /**
using System; * Digital Voice Modem - Audio Bridge
using System.Collections.Generic; * AGPLv3 Open Source. Use is subject to license terms.
using System.Linq; * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
using System.Net.Sockets; *
using System.Net; * @package DVM / Audio Bridge
using System.Text; * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
using System.Threading.Tasks; *
* Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL
*
*/
using fnecore.DMR; using fnecore.DMR;
using fnecore; using fnecore;
using NAudio.Wave; using NAudio.Wave;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// Implements a FNE system base. /// Implements a FNE system base.

@ -1,12 +1,20 @@
using System; // SPDX-License-Identifier: AGPL-3.0-only
using System.Collections.Generic; /**
using System.Linq; * Digital Voice Modem - Audio Bridge
using System.Text; * AGPLv3 Open Source. Use is subject to license terms.
using System.Threading.Tasks; * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Audio Bridge
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL
*
*/
using fnecore.NXDN; using fnecore.NXDN;
using fnecore; using fnecore;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// Implements a FNE system base. /// Implements a FNE system base.

@ -8,25 +8,14 @@
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0) * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* *
* Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2025 Caleb, K4PHP
* *
*/ */
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Serilog;
using fnecore; using fnecore;
using fnecore.P25; using fnecore.P25;
using NAudio.Wave; namespace DVMConsole
using System.Windows.Threading;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
namespace WhackerLinkConsoleV2
{ {
/// <summary> /// <summary>
/// Implements a FNE system base. /// Implements a FNE system base.
@ -366,7 +355,6 @@ namespace WhackerLinkConsoleV2
{ {
dfsiFrame[1U] = cryptoParams.AlgId; // Algorithm ID dfsiFrame[1U] = cryptoParams.AlgId; // Algorithm ID
FneUtils.WriteBytes(cryptoParams.KeyId, ref dfsiFrame, 2); // Key ID FneUtils.WriteBytes(cryptoParams.KeyId, ref dfsiFrame, 2); // Key ID
//dfsiFrame[3U] = 0;
Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE
} }
break; break;

@ -1,13 +1,22 @@
using System; // SPDX-License-Identifier: AGPL-3.0-only
using System.Collections.Generic; /**
using System.Linq; * Digital Voice Modem - DVMConsole
using System.Text; * AGPLv3 Open Source. Use is subject to license terms.
using System.Threading.Tasks; * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DVM Console
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL
* Copyright (C) 2025 Caleb, K4PHP
*
*/
using fnecore.DMR; using fnecore.DMR;
using fnecore; using fnecore;
using fnecore.P25.kmm; using fnecore.P25.kmm;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// Represents the individual timeslot data status. /// Represents the individual timeslot data status.
@ -97,14 +106,10 @@ namespace WhackerLinkConsoleV2
/// </summary> /// </summary>
public abstract partial class FneSystemBase : fnecore.FneSystemBase public abstract partial class FneSystemBase : fnecore.FneSystemBase
{ {
public List<byte[]> processedChunks = new List<byte[]>();
private Random rand; private Random rand;
internal MainWindow mainWindow; internal MainWindow mainWindow;
// List of active calls public List<byte[]> processedChunks = new List<byte[]>();
private List<(uint, byte)> activeTalkgroups = new List<(uint, byte)>();
/* /*
** Methods ** Methods

@ -1,27 +1,17 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkLib /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * Copyright (C) 2025 Caleb, K4PHP
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024-2025 Caleb, K4PHP
* *
*/ */
using WhackerLinkLib.Models.Radio; namespace DVMConsole
using WhackerLinkLib.Network;
namespace WhackerLinkConsoleV2
{ {
/// <summary> /// <summary>
/// WhackerLink peer/client websocket manager for having multiple systems /// WhackerLink peer/client websocket manager for having multiple systems

@ -1,18 +1,11 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* Copyright (C) 2025 Caleb, K4PHP * Copyright (C) 2025 Caleb, K4PHP
* *
@ -22,7 +15,7 @@ using NAudio.Wave;
using NAudio.Wave.SampleProviders; using NAudio.Wave.SampleProviders;
using System; using System;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public class GainSampleProvider : ISampleProvider public class GainSampleProvider : ISampleProvider
{ {

@ -1,4 +1,4 @@
<Window x:Class="WhackerLinkConsoleV2.KeyStatusWindow" <Window x:Class="DVMConsole.KeyStatusWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

@ -1,10 +1,21 @@
using System; // SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DVM Console
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Windows; using System.Windows;
using WhackerLinkConsoleV2.Controls; using DVMConsole.Controls;
using WhackerLinkLib.Models.Radio;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public partial class KeyStatusWindow : Window public partial class KeyStatusWindow : Window
{ {
@ -56,9 +67,6 @@ namespace WhackerLinkConsoleV2
continue; continue;
} }
if (!system.IsDvm)
continue;
if (cpgChannel.GetKeyId() == 0 || cpgChannel.GetAlgoId() == 0) if (cpgChannel.GetKeyId() == 0 || cpgChannel.GetAlgoId() == 0)
continue; continue;

@ -8,7 +8,7 @@ using System.Threading.Tasks;
using NWaves.Signals; using NWaves.Signals;
using NWaves.Transforms; using NWaves.Transforms;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public class MBEToneDetector public class MBEToneDetector
{ {

@ -1,8 +1,8 @@
<Window x:Class="WhackerLinkConsoleV2.MainWindow" <Window x:Class="DVMConsole.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WhackerLinkConsoleV2.Controls" xmlns:local="clr-namespace:DVMConsole.Controls"
Title="WhackerLinkConsoleV2" Height="600" Width="1000" Background="#FFF2F2F2"> Title="DVMConsole" Height="600" Width="1000" Background="#FFF2F2F2">
<Grid Background="#FFF2F2F2"> <Grid Background="#FFF2F2F2">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="497*"/> <ColumnDefinition Width="497*"/>
@ -60,7 +60,7 @@
</Button.Background> </Button.Background>
</Button> </Button>
<Image HorizontalAlignment="Left" Height="50" Grid.Row="1" VerticalAlignment="Center" Width="231" Source="/Assets/WhackerLinkLogoV4.png" Stretch="UniformToFill" Margin="5,0,0,0" RenderTransformOrigin="0.5,0.5"> <Image HorizontalAlignment="Left" Height="50" Grid.Row="1" VerticalAlignment="Center" Width="231" Source="/Assets/DvmLogo.png" Stretch="UniformToFill" Margin="5,0,0,0" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform> <Image.RenderTransform>
<TransformGroup> <TransformGroup>
<ScaleTransform/> <ScaleTransform/>
@ -148,7 +148,7 @@
</LinearGradientBrush> </LinearGradientBrush>
</Button.Background> </Button.Background>
</Button> </Button>
<Image HorizontalAlignment="Left" Margin="240,3,0,3" Grid.Row="1" Width="41" Source="pack://application:,,,/WhackerLinkConsoleV2;component/Assets/config2.png" IsHitTestVisible="False" RenderTransformOrigin="0.5,0.598"/> <Image HorizontalAlignment="Left" Margin="240,3,0,3" Grid.Row="1" Width="41" Source="/Assets/config2.png" IsHitTestVisible="False" RenderTransformOrigin="0.5,0.598"/>
<Button Content="" VerticalContentAlignment="Bottom" HorizontalAlignment="Left" Margin="510,0,0,0" VerticalAlignment="Center" Height="46" Width="44" Click="P25Page_Click" BorderBrush="#FFC1C1C1" BorderThickness="1,1,1,1" Grid.Row="1" FontSize="10" FontFamily="Arial"> <Button Content="" VerticalContentAlignment="Bottom" HorizontalAlignment="Left" Margin="510,0,0,0" VerticalAlignment="Center" Height="46" Width="44" Click="P25Page_Click" BorderBrush="#FFC1C1C1" BorderThickness="1,1,1,1" Grid.Row="1" FontSize="10" FontFamily="Arial">
<Button.Resources> <Button.Resources>
<Style TargetType="{x:Type Border}"> <Style TargetType="{x:Type Border}">

@ -1,18 +1,11 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* Copyright (C) 2024-2025 Caleb, K4PHP * Copyright (C) 2024-2025 Caleb, K4PHP
* Copyright (C) 2025 J. Dean * Copyright (C) 2025 J. Dean
@ -26,32 +19,19 @@ using System.IO;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using WhackerLinkLib.Models.Radio;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
using WhackerLinkConsoleV2.Controls; using DVMConsole.Controls;
using WebSocketManager = WhackerLinkLib.Managers.WebSocketManager;
using System.Windows.Media; using System.Windows.Media;
using WhackerLinkLib.Utils;
using WhackerLinkLib.Models;
using System.Net;
using NAudio.Wave; using NAudio.Wave;
using WhackerLinkLib.Interfaces;
using WhackerLinkLib.Models.IOSP;
using fnecore.P25; using fnecore.P25;
using fnecore; using fnecore;
using Microsoft.VisualBasic;
using System.Text;
using Nancy;
using Constants = fnecore.Constants; using Constants = fnecore.Constants;
using System.Security.Cryptography;
using fnecore.P25.LC.TSBK; using fnecore.P25.LC.TSBK;
using WebSocketSharp;
using NWaves.Signals; using NWaves.Signals;
using static WhackerLinkConsoleV2.P25Crypto; using static DVMConsole.P25Crypto;
using static WhackerLinkLib.Models.Radio.Codeplug;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
@ -60,6 +40,8 @@ namespace WhackerLinkConsoleV2
private bool globalPttState = false; private bool globalPttState = false;
private const int GridSize = 5;
private UIElement _draggedElement; private UIElement _draggedElement;
private Point _startPoint; private Point _startPoint;
private double _offsetX; private double _offsetX;
@ -70,7 +52,6 @@ namespace WhackerLinkConsoleV2
private SelectedChannelsManager _selectedChannelsManager; private SelectedChannelsManager _selectedChannelsManager;
private FlashingBackgroundManager _flashingManager; private FlashingBackgroundManager _flashingManager;
private WaveFilePlaybackManager _emergencyAlertPlayback; private WaveFilePlaybackManager _emergencyAlertPlayback;
private WebSocketManager _webSocketManager = new WebSocketManager();
private ChannelBox playbackChannelBox; private ChannelBox playbackChannelBox;
@ -88,12 +69,6 @@ namespace WhackerLinkConsoleV2
private Dictionary<string, SlotStatus> systemStatuses = new Dictionary<string, SlotStatus>(); private Dictionary<string, SlotStatus> systemStatuses = new Dictionary<string, SlotStatus>();
private FneSystemManager _fneSystemManager = new FneSystemManager(); private FneSystemManager _fneSystemManager = new FneSystemManager();
private bool cryptodev = true;
private static HashSet<uint> usedRids = new HashSet<uint>();
List<Tuple<uint, uint>> fneAffs = new List<Tuple<uint, uint>>();
public MainWindow() public MainWindow()
{ {
#if !DEBUG #if !DEBUG
@ -206,77 +181,6 @@ namespace WhackerLinkConsoleV2
if (File.Exists(system.AliasPath)) if (File.Exists(system.AliasPath))
system.RidAlias = AliasTools.LoadAliases(system.AliasPath); system.RidAlias = AliasTools.LoadAliases(system.AliasPath);
if (!system.IsDvm)
{
_webSocketManager.AddWebSocketHandler(system.Name);
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
handler.OnVoiceChannelResponse += HandleVoiceResponse;
handler.OnVoiceChannelRelease += HandleVoiceRelease;
handler.OnEmergencyAlarmResponse += HandleEmergencyAlarmResponse;
handler.OnAudioData += HandleReceivedAudio;
handler.OnAffiliationUpdate += HandleAffiliationUpdate;
handler.OnUnitRegistrationResponse += (response) =>
{
Dispatcher.Invoke(() =>
{
if (response.Status == (int)ResponseType.GRANT)
{
systemStatusBox.Background = (Brush)new BrushConverter().ConvertFrom("#FF00BC48");
systemStatusBox.ConnectionState = "Connected";
}
else
{
systemStatusBox.Background = new SolidColorBrush(Colors.Red);
systemStatusBox.ConnectionState = "Disconnected";
}
});
};
handler.OnClose += () =>
{
Dispatcher.Invoke(() =>
{
systemStatusBox.Background = new SolidColorBrush(Colors.Red);
systemStatusBox.ConnectionState = "Disconnected";
});
};
handler.OnOpen += () =>
{
Console.WriteLine("Peer connected");
};
handler.OnReconnecting += () =>
{
Console.WriteLine("Peer reconnecting");
};
Task.Run(() =>
{
handler.Connect(system.Address, system.Port, system.AuthKey);
handler.OnGroupAffiliationResponse += (response) => { /* TODO */ };
if (handler.IsConnected)
{
U_REG_REQ release = new U_REG_REQ
{
SrcId = system.Rid,
Site = system.Site
};
handler.SendMessage(release.GetData());
}
else
{
systemStatusBox.Background = new SolidColorBrush(Colors.Red);
systemStatusBox.ConnectionState = "Disconnected";
}
});
} else
{
_fneSystemManager.AddFneSystem(system.Name, system, this); _fneSystemManager.AddFneSystem(system.Name, system, this);
PeerSystem peer = _fneSystemManager.GetFneSystem(system.Name); PeerSystem peer = _fneSystemManager.GetFneSystem(system.Name);
@ -308,7 +212,6 @@ namespace WhackerLinkConsoleV2
{ {
peer.Start(); peer.Start();
}); });
}
if (!_settingsManager.ShowSystemStatus) if (!_settingsManager.ShowSystemStatus)
systemStatusBox.Visibility = Visibility.Collapsed; systemStatusBox.Visibility = Visibility.Collapsed;
@ -441,37 +344,6 @@ namespace WhackerLinkConsoleV2
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
Task.Run(() => Task.Run(() =>
{
if (!system.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (channel.IsSelected && channel.VoiceChannel != null && channel.PttState)
{
isAnyTgOn = true;
object voicePaket = new
{
type = PacketType.AUDIO_DATA,
data = new
{
Data = e.Buffer,
VoiceChannel = new VoiceChannel
{
Frequency = channel.VoiceChannel,
DstId = cpgChannel.Tgid,
SrcId = system.Rid,
Site = system.Site
},
Site = system.Site
}
};
handler.SendMessage(voicePaket);
}
}
else
{ {
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name); PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
@ -495,7 +367,6 @@ namespace WhackerLinkConsoleV2
} }
} }
} }
}
}); });
} }
@ -513,55 +384,14 @@ namespace WhackerLinkConsoleV2
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (!system.IsDvm) {
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (channel.IsSelected && handler.IsConnected)
{
Console.WriteLine("sending WLINK master aff");
Task.Run(() =>
{
GRP_AFF_REQ release = new GRP_AFF_REQ
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Site = system.Site
};
handler.SendMessage(release.GetData());
});
}
} else
{
PeerSystem fne = _fneSystemManager.GetFneSystem(system.Name); PeerSystem fne = _fneSystemManager.GetFneSystem(system.Name);
if (channel.IsSelected) if (channel.IsSelected)
{ {
uint newTgid = UInt32.Parse(cpgChannel.Tgid); uint newTgid = UInt32.Parse(cpgChannel.Tgid);
bool exists = fneAffs.Any(aff => aff.Item2 == newTgid);
if (cpgChannel.GetAlgoId() != 0 && cpgChannel.GetKeyId() != 0) if (cpgChannel.GetAlgoId() != 0 && cpgChannel.GetKeyId() != 0)
fne.peer.SendMasterKeyRequest(cpgChannel.GetAlgoId(), cpgChannel.GetKeyId()); fne.peer.SendMasterKeyRequest(cpgChannel.GetAlgoId(), cpgChannel.GetKeyId());
if (!exists)
fneAffs.Add(new Tuple<uint, uint>(GetUniqueRid(system.Rid), newTgid));
//Console.WriteLine("FNE Affiliations:");
//foreach (var aff in fneAffs)
//{
// Console.WriteLine($" RID: {aff.Item1}, TGID: {aff.Item2}");
//}
}
}
}
foreach (Codeplug.System system in Codeplug.Systems)
{
if (system.IsDvm)
{
PeerSystem fne = _fneSystemManager.GetFneSystem(system.Name);
//fne.peer.SendMasterAffiliationUpdate(fneAffs);
} }
} }
} }
@ -579,20 +409,6 @@ namespace WhackerLinkConsoleV2
DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems); DigitalPageWindow pageWindow = new DigitalPageWindow(Codeplug.Systems);
pageWindow.Owner = this; pageWindow.Owner = this;
if (pageWindow.ShowDialog() == true) if (pageWindow.ShowDialog() == true)
{
if (!pageWindow.RadioSystem.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(pageWindow.RadioSystem.Name);
CALL_ALRT_REQ callAlert = new CALL_ALRT_REQ
{
SrcId = pageWindow.RadioSystem.Rid,
DstId = pageWindow.DstId
};
handler.SendMessage(callAlert.GetData());
}
else
{ {
PeerSystem handler = _fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name); PeerSystem handler = _fneSystemManager.GetFneSystem(pageWindow.RadioSystem.Name);
IOSP_CALL_ALRT callAlert = new IOSP_CALL_ALRT(UInt32.Parse(pageWindow.DstId), UInt32.Parse(pageWindow.RadioSystem.Rid)); IOSP_CALL_ALRT callAlert = new IOSP_CALL_ALRT(UInt32.Parse(pageWindow.DstId), UInt32.Parse(pageWindow.RadioSystem.Rid));
@ -614,7 +430,6 @@ namespace WhackerLinkConsoleV2
Console.WriteLine("sent page"); Console.WriteLine("sent page");
} }
} }
}
private async void ManualPage_Click(object sender, RoutedEventArgs e) private async void ManualPage_Click(object sender, RoutedEventArgs e)
{ {
@ -626,6 +441,7 @@ namespace WhackerLinkConsoleV2
{ {
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
if (channel.PageState) if (channel.PageState)
{ {
@ -641,10 +457,7 @@ namespace WhackerLinkConsoleV2
Buffer.BlockCopy(toneA, 0, combinedAudio, 0, toneA.Length); Buffer.BlockCopy(toneA, 0, combinedAudio, 0, toneA.Length);
Buffer.BlockCopy(toneB, 0, combinedAudio, toneA.Length, toneB.Length); Buffer.BlockCopy(toneB, 0, combinedAudio, toneA.Length, toneB.Length);
int chunkSize = 1600; int chunkSize = 320;
if (system.IsDvm)
chunkSize = 320;
int totalChunks = (combinedAudio.Length + chunkSize - 1) / chunkSize; int totalChunks = (combinedAudio.Length + chunkSize - 1) / chunkSize;
@ -664,61 +477,17 @@ namespace WhackerLinkConsoleV2
byte[] chunk = new byte[chunkSize]; byte[] chunk = new byte[chunkSize];
Buffer.BlockCopy(combinedAudio, offset, chunk, 0, size); Buffer.BlockCopy(combinedAudio, offset, chunk, 0, size);
if (!system.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
AudioPacket voicePacket = new AudioPacket
{
Data = chunk,
VoiceChannel = new VoiceChannel
{
Frequency = channel.VoiceChannel,
DstId = cpgChannel.Tgid,
SrcId = system.Rid,
Site = system.Site
},
Site = system.Site,
LopServerVocode = true
};
handler.SendMessage(voicePacket.GetData());
} else
{
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
if (chunk.Length == 320) if (chunk.Length == 320)
{ {
P25EncodeAudioFrame(chunk, handler, channel, cpgChannel, system); P25EncodeAudioFrame(chunk, handler, channel, cpgChannel, system);
} }
} }
}
}); });
double totalDurationMs = (toneADuration + toneBDuration) * 1000 + 750; double totalDurationMs = (toneADuration + toneBDuration) * 1000 + 750;
await Task.Delay((int)totalDurationMs); await Task.Delay((int)totalDurationMs + 4000);
if (!system.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
GRP_VCH_RLS release = new GRP_VCH_RLS
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Channel = channel.VoiceChannel,
Site = system.Site
};
handler.SendMessage(release.GetData());
} else
{
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
await Task.Delay(4000);
handler.SendP25TDU(UInt32.Parse(system.Rid), UInt32.Parse(cpgChannel.Tgid), false); handler.SendP25TDU(UInt32.Parse(system.Rid), UInt32.Parse(cpgChannel.Tgid), false);
}
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
@ -748,6 +517,7 @@ namespace WhackerLinkConsoleV2
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
if (channel.PageState || (forHold && channel.HoldState)) if (channel.PageState || (forHold && channel.HoldState))
{ {
@ -795,28 +565,6 @@ namespace WhackerLinkConsoleV2
byte[] chunk = new byte[chunkSize]; byte[] chunk = new byte[chunkSize];
Buffer.BlockCopy(pcmData, offset, chunk, 0, chunkSize); Buffer.BlockCopy(pcmData, offset, chunk, 0, chunkSize);
if (!system.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
AudioPacket voicePacket = new AudioPacket
{
Data = chunk,
VoiceChannel = new VoiceChannel
{
Frequency = channel.VoiceChannel,
DstId = cpgChannel.Tgid,
SrcId = system.Rid,
Site = system.Site
},
Site = system.Site,
LopServerVocode = true
};
handler.SendMessage(voicePacket.GetData());
}
else
{
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name); PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
channel.chunkedPcm = AudioConverter.SplitToChunks(chunk); channel.chunkedPcm = AudioConverter.SplitToChunks(chunk);
@ -828,7 +576,6 @@ namespace WhackerLinkConsoleV2
P25EncodeAudioFrame(smallchunk, handler, channel, cpgChannel, system); P25EncodeAudioFrame(smallchunk, handler, channel, cpgChannel, system);
} }
} }
}
DateTime nextPacketTime = startTime.AddMilliseconds((i + 1) * 100); DateTime nextPacketTime = startTime.AddMilliseconds((i + 1) * 100);
TimeSpan waitTime = nextPacketTime - DateTime.UtcNow; TimeSpan waitTime = nextPacketTime - DateTime.UtcNow;
@ -840,34 +587,7 @@ namespace WhackerLinkConsoleV2
} }
double totalDurationMs = ((double)pcmData.Length / 16000) + 250; double totalDurationMs = ((double)pcmData.Length / 16000) + 250;
await Task.Delay((int)totalDurationMs); await Task.Delay((int)totalDurationMs + 3000);
if (!system.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
GRP_VCH_RLS release = new GRP_VCH_RLS
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Channel = channel.VoiceChannel,
Site = system.Site
};
Dispatcher.Invoke(() =>
{
handler.SendMessage(release.GetData());
if (forHold)
channel.PttButton.Background = channel.grayGradient;
else
channel.PageState = false;
});
} else
{
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
await Task.Delay(3000);
handler.SendP25TDU(UInt32.Parse(system.Rid), UInt32.Parse(cpgChannel.Tgid), false); handler.SendP25TDU(UInt32.Parse(system.Rid), UInt32.Parse(cpgChannel.Tgid), false);
@ -878,7 +598,6 @@ namespace WhackerLinkConsoleV2
else else
channel.PageState = false; channel.PageState = false;
}); });
}
}); });
} }
} }
@ -909,7 +628,7 @@ namespace WhackerLinkConsoleV2
} }
} }
private void HandleEmergencyAlarmResponse(EMRG_ALRM_RSP response) private void HandleEmergency(string dstId, string srcId)
{ {
bool forUs = false; bool forUs = false;
@ -918,11 +637,11 @@ namespace WhackerLinkConsoleV2
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (response.DstId == cpgChannel.Tgid) if (dstId == cpgChannel.Tgid)
{ {
forUs = true; forUs = true;
channel.Emergency = true; channel.Emergency = true;
channel.LastSrcId = response.SrcId; channel.LastSrcId = srcId;
} }
} }
@ -936,180 +655,10 @@ namespace WhackerLinkConsoleV2
} }
} }
private void HandleReceivedAudio(AudioPacket audioPacket)
{
bool shouldReceive = false;
string talkgroupId = audioPacket.VoiceChannel.DstId;
foreach (ChannelBox channel in _selectedChannelsManager.GetSelectedChannels())
{
if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
continue;
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (system.IsDvm)
continue;
if (audioPacket.VoiceChannel.SrcId != system.Rid && audioPacket.VoiceChannel.Frequency == channel.VoiceChannel && audioPacket.VoiceChannel.DstId == cpgChannel.Tgid)
shouldReceive = true;
}
if (shouldReceive)
_audioManager.AddTalkgroupStream(talkgroupId, audioPacket.Data);
}
private void HandleAffiliationUpdate(AFF_UPDATE affUpdate)
{
foreach (ChannelBox channel in _selectedChannelsManager.GetSelectedChannels())
{
if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
continue;
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (system.IsDvm)
continue;
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
bool ridExists = affUpdate.Affiliations.Any(aff => aff.SrcId == system.Rid);
bool tgidExists = affUpdate.Affiliations.Any(aff => aff.DstId == cpgChannel.Tgid);
if (ridExists && tgidExists)
{
//Console.WriteLine("rid aff'ed");
}
else
{
//Console.WriteLine("rid not aff'ed");
Task.Run(() =>
{
GRP_AFF_REQ affReq = new GRP_AFF_REQ
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Site = system.Site
};
handler.SendMessage(affReq.GetData());
});
}
}
}
private void HandleVoiceRelease(GRP_VCH_RLS response)
{
foreach (ChannelBox channel in _selectedChannelsManager.GetSelectedChannels())
{
if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
continue;
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (system.IsDvm)
continue;
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (response.DstId == cpgChannel.Tgid && response.SrcId != system.Rid)
{
Dispatcher.Invoke(() =>
{
if (channel.IsSelected)
{
channel.Background = (Brush)new BrushConverter().ConvertFrom("#FF0B004B");
channel.IsReceiving = false;
}
else
{
channel.Background = new SolidColorBrush(Colors.DarkGray);
channel.IsReceiving = false;
}
});
channel.VoiceChannel = null;
}
}
}
private void HandleVoiceResponse(GRP_VCH_RSP response)
{
foreach (ChannelBox channel in _selectedChannelsManager.GetSelectedChannels())
{
if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
continue;
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (system.IsDvm)
continue;
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (channel.PttState && response.Status == (int)ResponseType.GRANT && response.Channel != null && response.SrcId == system.Rid && response.DstId == cpgChannel.Tgid)
{
channel.VoiceChannel = response.Channel;
}
else if (response.Status == (int)ResponseType.GRANT && response.SrcId != system.Rid && response.DstId == cpgChannel.Tgid)
{
channel.VoiceChannel = response.Channel;
string alias = string.Empty;
try
{
alias = AliasTools.GetAliasByRid(system.RidAlias, int.Parse(response.SrcId));
}
catch (Exception) { }
if (alias.IsNullOrEmpty())
channel.LastSrcId = "Last SRC: " + response.SrcId;
else
channel.LastSrcId = "Last: " + alias;
Dispatcher.Invoke(() =>
{
channel.Background = (Brush)new BrushConverter().ConvertFrom("#FF00BC48");
channel.IsReceiving = true;
});
}
else if ((channel.HoldState || channel.PageState) && response.Status == (int)ResponseType.GRANT && response.Channel != null && response.SrcId == system.Rid && response.DstId == cpgChannel.Tgid)
{
channel.VoiceChannel = response.Channel;
}
else
{
//Dispatcher.Invoke(() =>
//{
// if (channel.IsSelected)
// channel.Background = new SolidColorBrush(Colors.DodgerBlue);
// else
// channel.Background = new SolidColorBrush(Colors.Gray);
//});
//channel.VoiceChannel = null;
//_stopSending = true;
}
}
}
private void ChannelBox_HoldChannelButtonClicked(object sender, ChannelBox e) private void ChannelBox_HoldChannelButtonClicked(object sender, ChannelBox e)
{ {
if (e.SystemName == PLAYBACKSYS || e.ChannelName == PLAYBACKCHNAME || e.DstId == PLAYBACKTG) if (e.SystemName == PLAYBACKSYS || e.ChannelName == PLAYBACKCHNAME || e.DstId == PLAYBACKTG)
return; return;
Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName);
if (system.IsDvm)
return;
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
} }
private void ChannelBox_PageButtonClicked(object sender, ChannelBox e) private void ChannelBox_PageButtonClicked(object sender, ChannelBox e)
@ -1119,40 +668,8 @@ namespace WhackerLinkConsoleV2
Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName);
if (!system.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (e.PageState)
{
GRP_VCH_REQ request = new GRP_VCH_REQ
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Site = system.Site
};
handler.SendMessage(request.GetData());
}
else
{
GRP_VCH_RLS release = new GRP_VCH_RLS
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Channel = e.VoiceChannel,
Site = system.Site
};
handler.SendMessage(release.GetData());
e.VoiceChannel = null;
}
}
else
{
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name); PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
if (e.PageState) if (e.PageState)
{ {
handler.SendP25TDU(UInt32.Parse(system.Rid), UInt32.Parse(cpgChannel.Tgid), true); handler.SendP25TDU(UInt32.Parse(system.Rid), UInt32.Parse(cpgChannel.Tgid), true);
@ -1162,7 +679,6 @@ namespace WhackerLinkConsoleV2
handler.SendP25TDU(UInt32.Parse(system.Rid), UInt32.Parse(cpgChannel.Tgid), false); handler.SendP25TDU(UInt32.Parse(system.Rid), UInt32.Parse(cpgChannel.Tgid), false);
} }
} }
}
private void ChannelBox_PTTButtonClicked(object sender, ChannelBox e) private void ChannelBox_PTTButtonClicked(object sender, ChannelBox e)
{ {
@ -1171,40 +687,6 @@ namespace WhackerLinkConsoleV2
Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName);
if (!system.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (!e.IsSelected)
return;
if (e.PttState)
{
GRP_VCH_REQ request = new GRP_VCH_REQ
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Site = system.Site
};
handler.SendMessage(request.GetData());
}
else
{
GRP_VCH_RLS release = new GRP_VCH_RLS
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Channel = e.VoiceChannel,
Site = system.Site
};
handler.SendMessage(release.GetData());
e.VoiceChannel = null;
}
} else
{
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name); PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
if (!e.IsSelected) if (!e.IsSelected)
@ -1219,16 +701,13 @@ namespace WhackerLinkConsoleV2
{ {
e.txStreamId = handler.NewStreamId(); e.txStreamId = handler.NewStreamId();
Console.WriteLine("sending grant demand " + dstId);
handler.SendP25TDU(srcId, dstId, true); handler.SendP25TDU(srcId, dstId, true);
} }
else else
{ {
Console.WriteLine("sending terminator " + dstId);
handler.SendP25TDU(srcId, dstId, false); handler.SendP25TDU(srcId, dstId, false);
} }
} }
}
private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{ {
@ -1243,8 +722,6 @@ namespace WhackerLinkConsoleV2
element.CaptureMouse(); element.CaptureMouse();
} }
private const int GridSize = 5;
private void ChannelBox_MouseMove(object sender, MouseEventArgs e) private void ChannelBox_MouseMove(object sender, MouseEventArgs e)
{ {
if (!isEditMode || !_isDragging || _draggedElement == null) return; if (!isEditMode || !_isDragging || _draggedElement == null) return;
@ -1413,36 +890,6 @@ namespace WhackerLinkConsoleV2
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (!system.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (channel.HoldState && !channel.IsReceiving && !channel.PttState && !channel.PageState)
{
//Task.Factory.StartNew(async () =>
//{
Console.WriteLine("Sending channel hold beep");
Dispatcher.Invoke(() => { channel.PttButton.Background = channel.redGradient; });
GRP_VCH_REQ req = new GRP_VCH_REQ
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Site = system.Site
};
handler.SendMessage(req.GetData());
await Task.Delay(1000);
SendAlertTone("hold.wav", true);
// });
}
} else
{
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name); PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
if (channel.HoldState && !channel.IsReceiving && !channel.PttState && !channel.PageState) if (channel.HoldState && !channel.IsReceiving && !channel.PttState && !channel.PageState)
@ -1454,7 +901,6 @@ namespace WhackerLinkConsoleV2
} }
} }
} }
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e) protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{ {
@ -1511,53 +957,6 @@ namespace WhackerLinkConsoleV2
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (!system.IsDvm)
{
IPeer handler = _webSocketManager.GetWebSocketHandler(system.Name);
if (!channel.IsSelected)
continue;
if (globalPttState)
{
Dispatcher.Invoke(() =>
{
btnGlobalPtt.Background = channel.redGradient;
});
GRP_VCH_REQ request = new GRP_VCH_REQ
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Site = system.Site
};
channel.PttState = true;
handler.SendMessage(request.GetData());
}
else
{
Dispatcher.Invoke(() =>
{
btnGlobalPtt.Background = channel.grayGradient;
});
GRP_VCH_RLS release = new GRP_VCH_RLS
{
SrcId = system.Rid,
DstId = cpgChannel.Tgid,
Site = system.Site
};
channel.PttState = false;
handler.SendMessage(release.GetData());
}
} else
{
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name); PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
channel.txStreamId = handler.NewStreamId(); channel.txStreamId = handler.NewStreamId();
@ -1584,9 +983,6 @@ namespace WhackerLinkConsoleV2
} }
} }
} }
}
private void Button_Click(object sender, RoutedEventArgs e) { /* sub */ }
private void SelectAll_Click(object sender, RoutedEventArgs e) private void SelectAll_Click(object sender, RoutedEventArgs e)
{ {
@ -1892,19 +1288,12 @@ namespace WhackerLinkConsoleV2
channel.p25Errs = channel.decoder.decode(imbe, samples); channel.p25Errs = channel.decoder.decode(imbe, samples);
#endif #endif
if (emergency) if (emergency && !channel.Emergency)
{
if (!channel.Emergency)
{ {
Task.Run(() => Task.Run(() =>
{ {
HandleEmergencyAlarmResponse(new EMRG_ALRM_RSP HandleEmergency(e.SrcId.ToString(), e.DstId.ToString());
{
SrcId = e.SrcId.ToString(),
DstId = e.DstId.ToString()
}); });
});
}
} }
if (samples != null) if (samples != null)
@ -1932,28 +1321,6 @@ namespace WhackerLinkConsoleV2
} }
} }
private uint GetUniqueRid(string ridString)
{
uint rid;
// Try to parse the RID, default to 1000 if parsing fails
if (!UInt32.TryParse(ridString, out rid))
{
rid = 1000;
}
// Ensure uniqueness by incrementing if needed
while (usedRids.Contains(rid))
{
rid++;
}
// Store the new unique RID
usedRids.Add(rid);
return rid;
}
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
@ -1982,10 +1349,6 @@ namespace WhackerLinkConsoleV2
{ {
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (!system.IsDvm)
continue;
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name); PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
if (cpgChannel.GetKeyId() != 0 && cpgChannel.GetAlgoId() != 0) if (cpgChannel.GetKeyId() != 0 && cpgChannel.GetAlgoId() != 0)
@ -2027,9 +1390,6 @@ namespace WhackerLinkConsoleV2
bool isEmergency = false; bool isEmergency = false;
bool encrypted = false; bool encrypted = false;
if (!system.IsDvm)
continue;
PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name); PeerSystem handler = _fneSystemManager.GetFneSystem(system.Name);
if (!channel.IsEnabled) if (!channel.IsEnabled)
@ -2088,7 +1448,7 @@ namespace WhackerLinkConsoleV2
} }
catch (Exception) { } catch (Exception) { }
if (alias.IsNullOrEmpty()) if (string.IsNullOrEmpty(alias))
channel.LastSrcId = "Last SRC: " + e.SrcId; channel.LastSrcId = "Last SRC: " + e.SrcId;
else else
channel.LastSrcId = "Last: " + alias; channel.LastSrcId = "Last: " + alias;

@ -1,32 +1,24 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Derrived from https://github.com/boatbod/op25/op25/gr-op25_repeater/lib/p25_crypt_algs.cc
* Derrived from https://github.com/boatbod/op25/blob/master/op25/gr-op25_repeater/lib/op25_crypt_aes.cc
* *
* Copyright (C) 2025 Caleb, K4PHP * Copyright (C) 2025 Caleb, K4PHP
* *
*/ */
// TODO: Move to fnecore
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Linq; using System.Linq;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public class P25Crypto public class P25Crypto
{ {

@ -1,31 +1,22 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
/** /**
* Digital Voice Modem - Audio Bridge * Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms. * AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* @package DVM / Audio Bridge * @package DVM / DVM Console
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0) * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* *
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL * Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024 Caleb, KO4UYJ * Copyright (C) 2024-2025 Caleb, K4PHP
* *
*/ */
using System;
using System.Net;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading.Tasks;
using Serilog; using System.Net;
using fnecore; using fnecore;
using WhackerLinkLib.Models.Radio;
using static WhackerLinkLib.Models.Radio.Codeplug;
using Microsoft.AspNetCore.Mvc.Formatters;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// Implements a peer FNE router system. /// Implements a peer FNE router system.
@ -67,12 +58,12 @@ namespace WhackerLinkConsoleV2
} }
catch (FormatException) catch (FormatException)
{ {
IPAddress[] addresses = Dns.GetHostAddresses("fne.zone1.scan.stream"); IPAddress[] addresses = Dns.GetHostAddresses(system.Address);
if (addresses.Length > 0) if (addresses.Length > 0)
endpoint = new IPEndPoint(addresses[0], system.Port); endpoint = new IPEndPoint(addresses[0], system.Port);
} }
FnePeer peer = new FnePeer("WLINKCONSOLE", system.PeerId, endpoint, presharedKey); FnePeer peer = new FnePeer("DVMCONSOLE", system.PeerId, endpoint, presharedKey);
// set configuration parameters // set configuration parameters
peer.Passphrase = system.AuthKey; peer.Passphrase = system.AuthKey;
@ -81,8 +72,8 @@ namespace WhackerLinkConsoleV2
Details = new PeerDetails Details = new PeerDetails
{ {
ConventionalPeer = true, ConventionalPeer = true,
Software = "WLINKCONSOLE", Software = "DVMCONSOLE",
Identity = "CONS OP" Identity = "CONS OP" // TODO: Add to config
} }
}; };
@ -100,8 +91,7 @@ namespace WhackerLinkConsoleV2
/// <param name="e"></param> /// <param name="e"></param>
private static void Peer_PeerConnected(object sender, PeerConnectedEvent e) private static void Peer_PeerConnected(object sender, PeerConnectedEvent e)
{ {
//FnePeer peer = (FnePeer)sender; /* stub */
//peer.SendMasterGroupAffiliation(1, (uint)Program.Configuration.DestinationId);
} }
/// <summary> /// <summary>

@ -1,9 +1,9 @@
<Window x:Class="WhackerLinkConsoleV2.QuickCallPage" <Window x:Class="DVMConsole.QuickCallPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WhackerLinkConsoleV2" xmlns:local="clr-namespace:DVMConsole"
mc:Ignorable="d" mc:Ignorable="d"
Title="Manual QC2 Page" Height="300" Width="300"> Title="Manual QC2 Page" Height="300" Width="300">
<Grid> <Grid>

@ -1,5 +1,5 @@
/* /*
* WhackerLink - WhackerLinkConsoleV2 * WhackerLink - DVMConsole
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -20,7 +20,7 @@
using System.Windows; using System.Windows;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// Interaction logic for QuickCallPage.xaml /// Interaction logic for QuickCallPage.xaml

@ -10,11 +10,10 @@
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL * Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* *
*/ */
using System;
using NAudio.Wave; using NAudio.Wave;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// ///

@ -1,26 +1,19 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* Copyright (C) 2024 Caleb, K4PHP * Copyright (C) 2024 Caleb, K4PHP
* *
*/ */
using WhackerLinkConsoleV2.Controls; using DVMConsole.Controls;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public class SelectedChannelsManager public class SelectedChannelsManager
{ {

@ -1,18 +1,11 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* Copyright (C) 2024-2025 Caleb, K4PHP * Copyright (C) 2024-2025 Caleb, K4PHP
* *
@ -21,7 +14,7 @@
using System.IO; using System.IO;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public class SettingsManager public class SettingsManager
{ {

@ -1,4 +1,4 @@
<UserControl x:Class="WhackerLinkConsoleV2.Controls.SystemStatusBox" <UserControl x:Class="DVMConsole.Controls.SystemStatusBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="105" Height="55" Background="#FF0B004B" BorderBrush="Gray" BorderThickness="1"> Width="105" Height="55" Background="#FF0B004B" BorderBrush="Gray" BorderThickness="1">

@ -1,28 +1,21 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * Copyright (C) 2025 Caleb, K4PHP
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024 Caleb, K4PHP
* *
*/ */
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows.Controls; using System.Windows.Controls;
using WhackerLinkLib.Models;
namespace WhackerLinkConsoleV2.Controls namespace DVMConsole.Controls
{ {
public partial class SystemStatusBox : UserControl, INotifyPropertyChanged public partial class SystemStatusBox : UserControl, INotifyPropertyChanged
{ {

@ -1,26 +1,19 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * Copyright (C) 2024-2025 Caleb, K4PHP
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024 Caleb, K4PHP
* *
*/ */
using NAudio.Wave; using NAudio.Wave;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
/// <summary> /// <summary>
/// ///

@ -1,4 +1,4 @@
// From W3AXL console // From https://github.com/w3axl/rc2-dvm
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -8,12 +8,9 @@ using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using fnecore; using fnecore;
using Serilog;
using WhackerLinkLib;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public enum MBE_MODE public enum MBE_MODE
{ {
DMR_AMBE, //! DMR AMBE DMR_AMBE, //! DMR AMBE

@ -0,0 +1,88 @@
namespace DVMConsole
{
/// <summary>
/// From https://github.com/W3AXL/rc2-dvm/blob/main/rc2-dvm/Audio.cs
/// No license info or copyright header was present, but attempting to give credit where credit is due.
/// </summary>
public class VocoderToneLookupTable
{
/// <summary>
/// This lookup table was obtained by recording the single-tone frames output from a EF Johnson VP8000
/// </summary>
public static SortedDictionary<ushort, byte[]> IMBEToneFrames = new SortedDictionary<ushort, byte[]>()
{
{ 281, new byte[] { 0x10, 0xFF, 0xF9, 0x45, 0x31, 0xCC, 0x8A, 0xC4, 0x3C, 0x16, 0x4B } },
{ 313, new byte[] { 0x0C, 0xFF, 0xF9, 0x45, 0x31, 0xCC, 0x8A, 0xC4, 0x3C, 0x17, 0x48 } },
{ 344, new byte[] { 0x5, 0x7F, 0xBC, 0xD6, 0xC6, 0x75, 0x80, 0x43, 0x39, 0x53, 0x9F } },
{ 375, new byte[] { 0x1, 0x7F, 0xF3, 0x13, 0xF1, 0xD9, 0x8A, 0xAC, 0x1D, 0x8F, 0xC6 } },
{ 406, new byte[] { 0x24, 0xF7, 0x7C, 0x1F, 0x40, 0x78, 0x81, 0xA0, 0x50, 0xBA, 0x0F } },
{ 438, new byte[] { 0x20, 0xF6, 0x67, 0x81, 0xF2, 0x96, 0x82, 0xAE, 0xA5, 0x47, 0x5C } },
{ 469, new byte[] { 0x1C, 0xFE, 0x54, 0xE9, 0x48, 0x4D, 0x86, 0x88, 0x54, 0x4F, 0x52 } },
{ 500, new byte[] { 0x18, 0xDF, 0x94, 0x2A, 0x5F, 0x28, 0x86, 0x20, 0x0B, 0xF6, 0xF2 } },
{ 531, new byte[] { 0x14, 0xF6, 0xE7, 0x2, 0xA4, 0xC5, 0x86, 0x24, 0x31, 0x58, 0x9A } },
{ 563, new byte[] { 0x10, 0xDF, 0x60, 0x5C, 0x3D, 0x4B, 0x8C, 0x44, 0xEC, 0x20, 0x4A } },
{ 594, new byte[] { 0x0C, 0xDF, 0x60, 0x5C, 0x3D, 0x4B, 0x2C, 0x44, 0xAC, 0x39, 0x6C } },
{ 625, new byte[] { 0x0C, 0xDF, 0x60, 0x5C, 0x3D, 0x4B, 0x8C, 0x44, 0xEC, 0x61, 0x48 } },
{ 656, new byte[] { 0x9, 0x76, 0x98, 0x2C, 0x66, 0xF1, 0x82, 0x18, 0x35, 0x2F, 0x2B } },
{ 688, new byte[] { 0x5, 0x5C, 0x30, 0xE2, 0x82, 0x79, 0x88, 0x13, 0x9, 0x54, 0x9E } },
{ 719, new byte[] { 0x5, 0x5C, 0x31, 0xE0, 0x80, 0x78, 0x88, 0x11, 0x28, 0x4C, 0x1A } },
{ 750, new byte[] { 0x1, 0x61, 0xC2, 0x83, 0x1, 0xD9, 0x90, 0x2E, 0x98, 0x88, 0xCF } },
{ 781, new byte[] { 0x1, 0x61, 0xC3, 0x82, 0x0, 0xD8, 0x90, 0xA4, 0x12, 0x0A, 0x4B } },
{ 813, new byte[] { 0x15, 0x47, 0x9D, 0x1B, 0xDC, 0xED, 0x82, 0x20, 0x71, 0x1E, 0x98 } },
{ 844, new byte[] { 0x11, 0x50, 0xD9, 0xF7, 0xA9, 0x4F, 0x89, 0xC7, 0x9C, 0x13, 0x43 } },
{ 875, new byte[] { 0x0D, 0x50, 0xD3, 0xFD, 0xBC, 0x4D, 0xC5, 0xC4, 0x9C, 0x3, 0x4E } },
{ 906, new byte[] { 0x0D, 0x50, 0xD3, 0xFD, 0xBC, 0x4D, 0xC5, 0xC4, 0x1C, 0x13, 0x4A } },
{ 938, new byte[] { 0x0D, 0x50, 0xD3, 0xFD, 0xB4, 0x4F, 0xC5, 0xC5, 0x1C, 0x73, 0x41 } },
{ 969, new byte[] { 0x9, 0x23, 0x0B, 0x0D, 0xC4, 0xA5, 0xC8, 0xE8, 0xA8, 0x2A, 0x2D } },
{ 1000, new byte[] { 0x9, 0x23, 0x0B, 0x0D, 0xC4, 0xA5, 0xCA, 0xE8, 0x28, 0x0A, 0x32 } },
{ 1031, new byte[] { 0x5, 0x51, 0xFB, 0xCA, 0xCE, 0x49, 0xCC, 0x3, 0x25, 0x59, 0x97 } },
{ 1063, new byte[] { 0x5, 0x51, 0xFB, 0xCA, 0xCE, 0x49, 0xCC, 0x3, 0x25, 0x59, 0x94 } },
{ 1094, new byte[] { 0x5, 0x51, 0xFB, 0xCA, 0xCE, 0x49, 0x0C, 0x43, 0x5, 0x41, 0x90 } },
{ 1125, new byte[] { 0x1, 0xC7, 0x9B, 0x13, 0x31, 0x59, 0x80, 0xAA, 0x99, 0x89, 0xCF } },
{ 1156, new byte[] { 0x1, 0xE7, 0x1A, 0x12, 0x30, 0x58, 0x80, 0xA2, 0x91, 0x89, 0xCD } },
{ 1188, new byte[] { 0x1, 0xE7, 0x1A, 0x12, 0x30, 0x58, 0x80, 0xA2, 0x91, 0x89, 0xCA } },
{ 1219, new byte[] { 0x0D, 0x50, 0x93, 0x4D, 0x28, 0x0D, 0x4D, 0x85, 0xB4, 0x32, 0x43 } },
{ 1250, new byte[] { 0x0D, 0x50, 0x93, 0x4D, 0x28, 0x0D, 0x4D, 0x85, 0xB4, 0x32, 0x41 } },
{ 1281, new byte[] { 0x9, 0x24, 0x7D, 0x7B, 0xD4, 0xDD, 0x49, 0xB8, 0xB6, 0x2C, 0xA5 } },
{ 1313, new byte[] { 0x9, 0x24, 0x7D, 0x7B, 0xD4, 0xDD, 0x45, 0xB8, 0xB6, 0x2C, 0xAA } },
{ 1344, new byte[] { 0x9, 0x24, 0x7D, 0x7B, 0xD4, 0xDD, 0x45, 0xF8, 0xA6, 0x2A, 0xB8 } },
{ 1375, new byte[] { 0x5, 0xC1, 0x8A, 0xE8, 0x94, 0x2C, 0x41, 0x1D, 0x22, 0x45, 0x16 } },
{ 1406, new byte[] { 0x5, 0xC1, 0x8A, 0xE8, 0x98, 0x66, 0x40, 0x7D, 0x32, 0x5D, 0x14 } },
{ 1438, new byte[] { 0x5, 0xC1, 0x8A, 0xE8, 0x98, 0x66, 0x40, 0x7D, 0x32, 0x5D, 0x13 } },
{ 1469, new byte[] { 0x5, 0xC1, 0x8A, 0xE8, 0x98, 0x66, 0x40, 0x7D, 0x32, 0x57, 0x11 } },
{ 1500, new byte[] { 0x1, 0x2D, 0xA7, 0x2A, 0xDD, 0xA8, 0x5C, 0xC8, 0x5C, 0x49, 0x46 } },
{ 1531, new byte[] { 0x1, 0x2D, 0xA7, 0x2A, 0xDD, 0xA8, 0x5C, 0xC8, 0x5C, 0x49, 0x65 } },
{ 1563, new byte[] { 0x1, 0x2D, 0xA7, 0x2A, 0xDD, 0xA8, 0x5C, 0xC8, 0x58, 0x4F, 0x62 } },
{ 1594, new byte[] { 0x1, 0x2D, 0xA7, 0x2A, 0xDD, 0xA8, 0x5C, 0x48, 0xDC, 0xCB, 0xE3 } },
{ 1625, new byte[] { 0x9, 0x24, 0xF0, 0x6C, 0xC5, 0x55, 0x4B, 0xF0, 0x54, 0x30, 0x35 } },
{ 1656, new byte[] { 0x9, 0x24, 0xF0, 0x6C, 0xC5, 0x55, 0x4B, 0xF0, 0x44, 0x34, 0x23 } },
{ 1688, new byte[] { 0x9, 0x24, 0xF0, 0x6C, 0xC5, 0x55, 0x4B, 0xF0, 0x44, 0x24, 0x31 } },
{ 1719, new byte[] { 0x5, 0x0B, 0x69, 0xE9, 0x8D, 0x74, 0x4A, 0x0D, 0x0A, 0xC5, 0x5E } },
{ 1750, new byte[] { 0x5, 0x0B, 0x68, 0xEB, 0x8F, 0x65, 0x4A, 0x2F, 0x1B, 0xCD, 0xDD } },
{ 1781, new byte[] { 0x5, 0x0B, 0x68, 0xEB, 0x8F, 0x65, 0x4A, 0x2F, 0x1B, 0xCD, 0xDA } },
{ 1813, new byte[] { 0x5, 0x0B, 0x68, 0xEB, 0x8F, 0x65, 0x4A, 0x2F, 0x1B, 0xCD, 0xDB } },
{ 1844, new byte[] { 0x5, 0x0B, 0x68, 0xEB, 0x8F, 0x65, 0x4A, 0x3F, 0x13, 0xC1, 0x58 } },
{ 1875, new byte[] { 0x1, 0x2C, 0xA2, 0xA2, 0x55, 0x1, 0x5B, 0x0C, 0x92, 0x8D, 0xA7 } },
{ 1906, new byte[] { 0x1, 0x2C, 0xA2, 0xA2, 0x55, 0x1, 0x53, 0x0C, 0x92, 0x8F, 0xAC } },
{ 1938, new byte[] { 0x1, 0x2C, 0xA2, 0xA2, 0x55, 0x1, 0x53, 0x0C, 0x92, 0x8B, 0x2D } },
{ 1969, new byte[] { 0x1, 0x2C, 0xA2, 0xA2, 0x55, 0x1, 0x53, 0x0C, 0x92, 0x8A, 0x2B } },
{ 2000, new byte[] { 0x1, 0x2C, 0xA2, 0xA2, 0x55, 0x1, 0x53, 0x0C, 0x92, 0x83, 0x2A } },
{ 2031, new byte[] { 0x9, 0x0E, 0xD4, 0x38, 0xDF, 0x3D, 0x6C, 0xD4, 0x2F, 0x0E, 0xE8 } },
{ 2063, new byte[] { 0x5, 0x0B, 0x59, 0x4C, 0xC0, 0x48, 0x6C, 0x1C, 0x4E, 0x5C, 0x0E } },
{ 2094, new byte[] { 0x5, 0x0B, 0x58, 0x4E, 0xC2, 0x49, 0x6C, 0x1E, 0x4F, 0x5C, 0x8C } },
{ 2125, new byte[] { 0x5, 0x0B, 0x58, 0x4E, 0xD2, 0x41, 0x6C, 0x0E, 0x47, 0x58, 0x8D } },
{ 2156, new byte[] { 0x5, 0x0B, 0x58, 0x4E, 0xD2, 0x41, 0x6C, 0x0E, 0x47, 0x50, 0x82 } },
{ 2188, new byte[] { 0x5, 0x0B, 0x58, 0x4E, 0xD2, 0x41, 0x6C, 0x0E, 0x47, 0x40, 0x0 } },
{ 2219, new byte[] { 0x5, 0x0B, 0x58, 0x4E, 0xD2, 0x41, 0x6C, 0x0E, 0x47, 0x40, 0x1 } },
{ 2250, new byte[] { 0x1, 0xAA, 0x66, 0xBF, 0x5C, 0x4, 0x42, 0x60, 0xFA, 0x6D, 0xAE } },
{ 2281, new byte[] { 0x1, 0xAA, 0x66, 0xBF, 0x5C, 0x4, 0x42, 0x60, 0xFB, 0x6D, 0xAE } },
{ 2313, new byte[] { 0x1, 0xAA, 0x66, 0xBF, 0x5C, 0x4, 0x42, 0x60, 0xFA, 0x63, 0x2D } },
{ 2344, new byte[] { 0x1, 0xAA, 0x66, 0xBF, 0x5C, 0x4, 0x42, 0x62, 0xF8, 0x61, 0x2B } },
{ 2375, new byte[] { 0x1, 0xAA, 0x66, 0xBF, 0x5C, 0x4, 0x42, 0x62, 0xF9, 0x60, 0x2A } },
{ 2406, new byte[] { 0x5, 0x6, 0xFB, 0x63, 0xCD, 0xD9, 0x2B, 0x42, 0xE1, 0xD7, 0x6F } },
{ 2438, new byte[] { 0x5, 0x6, 0xFB, 0x63, 0xCD, 0xD9, 0x2B, 0x42, 0xE1, 0xC7, 0x6C } },
{ 2469, new byte[] { 0x5, 0x6, 0xFB, 0x63, 0xCD, 0xD9, 0x2B, 0x42, 0xE1, 0xCF, 0x6D } },
{ 2500, new byte[] { 0x5, 0x6, 0xFB, 0x63, 0xCD, 0xD9, 0x2B, 0x42, 0xE1, 0xCF, 0x6B } },
};
}
}

@ -1,18 +1,11 @@
/* // SPDX-License-Identifier: AGPL-3.0-only
* WhackerLink - WhackerLinkConsoleV2 /**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This program is free software: you can redistribute it and/or modify * @package DVM / DVM Console
* it under the terms of the GNU General Public License as published by * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
* Copyright (C) 2024 Caleb, K4PHP * Copyright (C) 2024 Caleb, K4PHP
* *
@ -21,7 +14,7 @@
using NAudio.Wave; using NAudio.Wave;
using System.Windows.Threading; using System.Windows.Threading;
namespace WhackerLinkConsoleV2 namespace DVMConsole
{ {
public class WaveFilePlaybackManager public class WaveFilePlaybackManager
{ {

@ -1,4 +1,4 @@
<Window x:Class="WhackerLinkConsoleV2.WidgetSelectionWindow" <Window x:Class="DVMConsole.WidgetSelectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Select Widgets" Height="200" Width="300" WindowStartupLocation="CenterOwner"> Title="Select Widgets" Height="200" Width="300" WindowStartupLocation="CenterOwner">

@ -0,0 +1,38 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - DVMConsole
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DVM Console
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2024 Caleb, K4PHP
*
*/
using System.Windows;
namespace DVMConsole
{
public partial class WidgetSelectionWindow : Window
{
public bool ShowSystemStatus { get; private set; } = true;
public bool ShowChannels { get; private set; } = true;
public bool ShowAlertTones { get; private set; } = true;
public WidgetSelectionWindow()
{
InitializeComponent();
}
private void ApplyButton_Click(object sender, RoutedEventArgs e)
{
ShowSystemStatus = SystemStatusCheckBox.IsChecked ?? false;
ShowChannels = ChannelCheckBox.IsChecked ?? false;
ShowAlertTones = AlertToneCheckBox.IsChecked ?? false;
DialogResult = true;
Close();
}
}
}

@ -0,0 +1,35 @@
systems:
- name: "System 1"
address: "localhost"
port: 3001
authKey: "RPT_PASSWORD"
peerId: 1234567
rid: "12345"
zones:
- name: "Zone 1"
channels:
- name: "Channel 1"
system: "System 1"
tgid: "2001"
keyId: 0x50
algoId: 0xaa # 0xAA or 0x84 (RC4 or AES)
encryptionKey: null # Ignored now, we use dvmfne KMM support
- name: "Channel 2"
system: "System 1"
tgid: "15002"
- name: "Channel 3"
system: "System 1"
tgid: "15003"
- name: "Zone 2"
channels:
- name: "Channel A"
system: "System 1"
tgid: "16001"
- name: "Channel B"
system: "System 1"
tgid: "16002"
- name: "Channel C"
system: "System 1"
tgid: "16002"

@ -1,5 +1,5 @@
GNU GENERAL PUBLIC LICENSE GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
@ -7,17 +7,15 @@
Preamble Preamble
The GNU General Public License is a free, copyleft license for The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works. software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast, to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the software for all its users.
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you price. Our General Public Licenses are designed to make sure that you
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things. free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you Developers that use our General Public Licenses protect your rights
these rights or asking you to surrender the rights. Therefore, you have with two steps: (1) assert copyright on the software, and (2) offer
certain responsibilities if you distribute copies of the software, or if you this License which gives you legal permission to copy, distribute
you modify it: responsibilities to respect the freedom of others. and/or modify the software.
For example, if you distribute copies of such a program, whether A secondary benefit of defending all users' freedom is that
gratis or for a fee, you must pass on to the recipients the same improvements made in alternate versions of the program, if they
freedoms that you received. You must make sure that they, too, receive receive widespread use, become available for other developers to
or can get the source code. And you must show them these terms so they incorporate. Many developers of free software are heartened and
know their rights. encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
Developers that use the GNU GPL protect your rights with two steps: The GNU General Public License permits making a modified version and
(1) assert copyright on the software, and (2) offer you this License letting the public access it on a server without ever releasing its
giving you legal permission to copy, distribute and/or modify it. source code to the public.
For the developers' and authors' protection, the GPL clearly explains The GNU Affero General Public License is designed specifically to
that there is no warranty for this free software. For both users' and ensure that, in such cases, the modified source code becomes available
authors' sake, the GPL requires that modified versions be marked as to the community. It requires the operator of a network server to
changed, so that their problems will not be attributed erroneously to provide the source code of the modified version running there to the
authors of previous versions. users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
Some devices are designed to deny users access to install or run code of the modified version.
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of An older license, called the Affero General Public License and
protecting users' freedom to change the software. The systematic published by Affero, was designed to accomplish similar goals. This is
pattern of such abuse occurs in the area of products for individuals to a different license, not a version of the Affero GPL, but Affero has
use, which is precisely where it is most unacceptable. Therefore, we released a new version of the Affero GPL which permits relicensing under
have designed this version of the GPL to prohibit the practice for those this license.
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and The precise terms and conditions for copying, distribution and
modification follow. modification follow.
@ -72,7 +60,7 @@ modification follow.
0. Definitions. 0. Definitions.
"This License" refers to version 3 of the GNU General Public License. "This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks. works, such as semiconductor masks.
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program. License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License. 13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work, License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License, but the work with which it is combined will remain governed by version
section 13, concerning interaction through a network will apply to the 3 of the GNU General Public License.
combination as such.
14. Revised Versions of this License. 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will the GNU Affero General Public License from time to time. Such new versions
be similar in spirit to the present version, but may differ in detail to will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns. address new problems or concerns.
Each version is given a distinguishing version number. If the Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation. by the Free Software Foundation.
If the Program specifies that a proxy can decide which future If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you public statement of acceptance of a version permanently authorizes you
to choose that version for the Program. to choose that version for the Program.
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author> Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short If your software can interact with users remotely through a computer
notice like this when it starts in an interactive mode: network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
<program> Copyright (C) <year> <name of author> interface could display a "Source" link that leads users to an archive
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. of the code. There are many ways you could offer source, and different
This is free software, and you are welcome to redistribute it solutions will be better for different programs; see section 13 for the
under certain conditions; type `show c' for details. specific requirements.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

@ -1,28 +0,0 @@
/*
* WhackerLink - WhackerLinkConsoleV2
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024 Caleb, K4PHP
*
*/
namespace WhackerLinkConsoleV2
{
public class ChannelPosition
{
public double X { get; set; }
public double Y { get; set; }
}
}

@ -1,36 +0,0 @@
/*
* WhackerLink - WhackerLinkConsoleV2
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
using System.Runtime.InteropServices;
namespace WhackerLinkConsoleV2
{
public static class ConsoleNative
{
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
public static void ShowConsole()
{
AllocConsole();
Console.WriteLine("Console attached.");
}
}
}

@ -1,45 +0,0 @@
/*
* WhackerLink - WhackerLinkConsoleV2
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2024 Caleb, K4PHP
*
*/
using System.Windows;
namespace WhackerLinkConsoleV2
{
public partial class WidgetSelectionWindow : Window
{
public bool ShowSystemStatus { get; private set; } = true;
public bool ShowChannels { get; private set; } = true;
public bool ShowAlertTones { get; private set; } = true;
public WidgetSelectionWindow()
{
InitializeComponent();
}
private void ApplyButton_Click(object sender, RoutedEventArgs e)
{
ShowSystemStatus = SystemStatusCheckBox.IsChecked ?? false;
ShowChannels = ChannelCheckBox.IsChecked ?? false;
ShowAlertTones = AlertToneCheckBox.IsChecked ?? false;
DialogResult = true;
Close();
}
}
}

@ -1,74 +0,0 @@
radioWide:
hostVersion: "R01.00.00"
codeplugVersion: "R01.00.00"
radioAlias: ""
serialNumber: "123ABC1234"
model: "CONSOLE"
inCarMode: "N/A"
systems:
- name: "System 1"
address: "localhost"
port: 3000
authKey: ""
rid: "12345"
site:
name: "Central Site"
controlChannel: "772.74375"
voiceChannels: []
location:
x: 757.89
y: 1274.17
z: 360.3
systemID: 1
siteID: 1
range: 1.5
# Example DVM Project FNE system
- name: "System 2"
address: "localhost"
port: 3001
authKey: "RPT_PASSWORD"
peerId: 1234567
isDvm: true
# Note: for proper operation with affiliated traffic, this id will use +1 for each channel you have for affiliation purposes
rid: "12345"
site:
name: "Central Site"
controlChannel: "772.74375"
voiceChannels: []
location:
x: 757.89
y: 1274.17
z: 360.3
systemID: 1
siteID: 1
range: 1.5
zones:
- name: "Zone 1"
channels:
- name: "Channel 1"
system: "System 1"
tgid: "2001"
keyId: 0x50
algoId: 0xaa # 0xAA or 0x84 (RC4 or AES)
encryptionKey: null # Ignored now, we use dvmfne KMM support
- name: "Channel 2"
system: "System 1"
tgid: "15002"
- name: "Channel 3"
system: "System 1"
tgid: "15003"
- name: "Zone 2"
channels:
- name: "Channel A"
system: "System 1"
tgid: "16001"
- name: "Channel B"
system: "System 1"
tgid: "16002"
- name: "Channel C"
system: "System 1"
tgid: "16002"

@ -1 +0,0 @@
Subproject commit 34ae0da1f296c1023455db9edc9b854f9b1eb0f2
Loading…
Cancel
Save

Powered by TurnKey Linux.