Initial commit
This commit is contained in:
commit
f9adc0bd60
27 changed files with 1957 additions and 0 deletions
351
.gitignore
vendored
Normal file
351
.gitignore
vendored
Normal file
|
@ -0,0 +1,351 @@
|
|||
# build dir
|
||||
build
|
||||
build*/
|
||||
|
||||
# options of configure.bat
|
||||
install-path.ini
|
||||
|
||||
!nuget.exe
|
||||
|
||||
############################################################################
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true
|
||||
**/wwwroot/lib/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
137
CMakeLists.txt
Normal file
137
CMakeLists.txt
Normal file
|
@ -0,0 +1,137 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(dsn_service LANGUAGES CSharp)
|
||||
|
||||
include(CSharpUtilities)
|
||||
set(CMAKE_CSharp_FLAGS "/langversion:6 /platform:anycpu /define:TRACE")
|
||||
|
||||
|
||||
###############
|
||||
# Source codes and Targets
|
||||
###############
|
||||
|
||||
file(GLOB_RECURSE DSN_SERVICE_SRC
|
||||
dsn_service/*.cs
|
||||
dsn_service/*.xml
|
||||
dsn_service/*.config
|
||||
dsn_service/*.txt
|
||||
)
|
||||
|
||||
add_executable(dsn_service ${DSN_SERVICE_SRC})
|
||||
|
||||
|
||||
###############
|
||||
# NuGet package restore
|
||||
###############
|
||||
|
||||
add_custom_target(Please_Reinstall_NuGet_Packages_Manually COMMAND
|
||||
echo ========================================================================================================================== &&
|
||||
echo For technical reasons, CMake can't automatically restore the NuGet package and update references for project dsn_service. &&
|
||||
echo If you met assembly missing, please restore NuGet packages manually and run this command in NuGet Package Manager Console: &&
|
||||
echo Update-Package -reinstall -projectname dsn_service &&
|
||||
echo ==========================================================================================================================
|
||||
)
|
||||
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/dsn_service/packages.config
|
||||
${CMAKE_CURRENT_BINARY_DIR}/packages.config
|
||||
COPYONLY
|
||||
)
|
||||
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/dsn_service/FodyWeavers.xml
|
||||
${CMAKE_CURRENT_BINARY_DIR}/FodyWeavers.xml
|
||||
COPYONLY
|
||||
)
|
||||
|
||||
add_dependencies(dsn_service Please_Reinstall_NuGet_Packages_Manually)
|
||||
|
||||
|
||||
###############
|
||||
# Project Information
|
||||
###############
|
||||
|
||||
set(VS_DOTNET_REFERENCES
|
||||
Microsoft.CSharp
|
||||
System
|
||||
System.Core
|
||||
System.Data
|
||||
System.Data.DataSetExtensions
|
||||
System.Net.Http
|
||||
System.Speech
|
||||
System.Web.Extensions
|
||||
System.Xml
|
||||
System.Xml.Linq
|
||||
)
|
||||
|
||||
set_target_properties(dsn_service PROPERTIES
|
||||
VS_GLOBAL_ProjectGuid "{DEA491EE-C426-4B79-A443-CF5B1D795288}"
|
||||
VS_DOTNET_TARGET_FRAMEWORK_VERSION "v4.6.1"
|
||||
VS_DOTNET_REFERENCES "${VS_DOTNET_REFERENCES}"
|
||||
OUTPUT_NAME "DragonbornSpeaksNaturally"
|
||||
)
|
||||
|
||||
|
||||
###############
|
||||
# Install and Package
|
||||
###############
|
||||
|
||||
if (SVR_DIR)
|
||||
set(SVR_PLUGIN_DIR "${SVR_DIR}/Data/Plugins/Sumwunn")
|
||||
message("-- SkyrimVR plugin install path: ${SVR_PLUGIN_DIR}/")
|
||||
|
||||
install(
|
||||
TARGETS dsn_service
|
||||
COMPONENT SkyrimVR
|
||||
RUNTIME DESTINATION ${SVR_PLUGIN_DIR}
|
||||
)
|
||||
add_custom_command(
|
||||
TARGET dsn_service POST_BUILD VERBATIM
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy "$<TARGET_FILE:dsn_service>" ${SVR_PLUGIN_DIR} &&
|
||||
echo file copied: "$<TARGET_FILE:dsn_service> -> ${SVR_PLUGIN_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if (SSE_DIR)
|
||||
set(SSE_PLUGIN_DIR "${SSE_DIR}/Data/Plugins/Sumwunn")
|
||||
message("-- SkyrimSE plugin install path: ${SSE_PLUGIN_DIR}/")
|
||||
|
||||
add_custom_command(
|
||||
TARGET dsn_service POST_BUILD VERBATIM
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E copy "$<TARGET_FILE:dsn_service>" ${SSE_PLUGIN_DIR} &&
|
||||
echo file copied: "$<TARGET_FILE:dsn_service> -> ${SSE_PLUGIN_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
#
|
||||
# ZIP Package
|
||||
#
|
||||
option(PACKAGE "Generate NMM/Vortex Compatible ZIP Package" ON)
|
||||
if (PACKAGE)
|
||||
if (NOT IS_SUB_PROJECT)
|
||||
message("-- Generate NMM/Vortex Compatible ZIP Package: On (-DPACKAGE=ON)")
|
||||
endif()
|
||||
|
||||
set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/package_tmp)
|
||||
install(
|
||||
TARGETS dsn_service
|
||||
RUNTIME DESTINATION SkyrimVR/Data/Plugins/Sumwunn
|
||||
)
|
||||
install(
|
||||
TARGETS dsn_service
|
||||
RUNTIME DESTINATION SkyrimSE/Data/Plugins/Sumwunn
|
||||
)
|
||||
|
||||
set(CPACK_GENERATOR ZIP)
|
||||
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)
|
||||
if (NOT IS_SUB_PROJECT)
|
||||
include(CPack)
|
||||
endif()
|
||||
else()
|
||||
if (NOT IS_SUB_PROJECT)
|
||||
message("-- Generate NMM/Vortex Compatible ZIP Package: Off (-DPACKAGE=OFF)")
|
||||
endif()
|
||||
endif()
|
||||
|
30
EmpathicQbt.ConsoleServer.sln
Normal file
30
EmpathicQbt.ConsoleServer.sln
Normal file
|
@ -0,0 +1,30 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31205.134
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmpathicQbt.ConsoleServer", "EmpathicQubit.ConsoleServer\EmpathicQbt.ConsoleServer.csproj", "{DEA491EE-C426-4B79-A443-CF5B1D795288}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C0E8A622-ACEB-4328-93B5-C8FE43D10E02}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DEA491EE-C426-4B79-A443-CF5B1D795288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DEA491EE-C426-4B79-A443-CF5B1D795288}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DEA491EE-C426-4B79-A443-CF5B1D795288}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DEA491EE-C426-4B79-A443-CF5B1D795288}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {82D28A7C-FEF8-480B-A2F5-EA6F564C5917}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
14
EmpathicQubit.ConsoleServer/App.config
Normal file
14
EmpathicQubit.ConsoleServer/App.config
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.5.0.0" newVersion="3.5.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
85
EmpathicQubit.ConsoleServer/Configuration.cs
Normal file
85
EmpathicQubit.ConsoleServer/Configuration.cs
Normal file
|
@ -0,0 +1,85 @@
|
|||
using IniParser;
|
||||
using IniParser.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer {
|
||||
|
||||
public class Configuration {
|
||||
private readonly string CONFIG_FILE_NAME = "DragonbornSpeaksNaturally.ini";
|
||||
|
||||
// NOTE: Relative to SkyrimVR.exe
|
||||
private static readonly string[] SEARCH_DIRECTORIES = {
|
||||
"Data\\Plugins\\Sumwunn\\",
|
||||
"..\\",
|
||||
""
|
||||
};
|
||||
|
||||
private string iniFilePath = null;
|
||||
|
||||
private IniData global = null;
|
||||
private IniData local = null;
|
||||
private IniData merged = null;
|
||||
|
||||
public Configuration() {
|
||||
iniFilePath = ResolveFilePath(CONFIG_FILE_NAME);
|
||||
|
||||
loadLocal();
|
||||
loadGlobal();
|
||||
|
||||
merged = new IniData();
|
||||
merged.Merge(global);
|
||||
merged.Merge(local);
|
||||
}
|
||||
|
||||
public string GetIniFilePath() {
|
||||
return iniFilePath;
|
||||
}
|
||||
|
||||
public string Get(string section, string key, string def) {
|
||||
string val = merged[section][key];
|
||||
if (val == null)
|
||||
return def;
|
||||
return val;
|
||||
}
|
||||
|
||||
private void loadGlobal() {
|
||||
global = new IniData();
|
||||
}
|
||||
|
||||
private void loadLocal() {
|
||||
local = loadIniFromFilePath(iniFilePath);
|
||||
if (local == null)
|
||||
local = new IniData();
|
||||
}
|
||||
|
||||
public static string ResolveFilePath(string filename) {
|
||||
foreach (string directory in SEARCH_DIRECTORIES) {
|
||||
string filepath = directory + filename;
|
||||
if (File.Exists(filepath)) {
|
||||
return Path.GetFullPath(filepath); ;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private IniData loadIniFromFilePath(string filepath) {
|
||||
if (filepath != null) {
|
||||
Trace.TraceInformation("Loading ini from path " + filepath);
|
||||
try {
|
||||
var parser = new FileIniDataParser();
|
||||
return parser.ReadFile(filepath);
|
||||
} catch (Exception ex) {
|
||||
Trace.TraceError("Failed to load ini file at " + filepath);
|
||||
Trace.TraceError(ex.ToString());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
68
EmpathicQubit.ConsoleServer/ConsoleInput.cs
Normal file
68
EmpathicQubit.ConsoleServer/ConsoleInput.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer
|
||||
{
|
||||
public class ConsoleInput
|
||||
{
|
||||
|
||||
private BlockingCollection<string> inputQueue = new BlockingCollection<string>();
|
||||
private Thread inputThread = null;
|
||||
bool isInputTerminated = false;
|
||||
|
||||
// Saved state, used to restore after reloading the configuration file.
|
||||
public string currentFavoritesList = null;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
inputThread = new Thread(ReadLineFromConsole);
|
||||
inputThread.Start();
|
||||
}
|
||||
|
||||
private void ReadLineFromConsole()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
string input = Console.ReadLine();
|
||||
|
||||
// input will be null when Skyrim terminated (stdin closed)
|
||||
if (input == null)
|
||||
{
|
||||
isInputTerminated = true;
|
||||
Trace.TraceInformation("Skyrim is terminated, console server will quit.");
|
||||
|
||||
// Notify the SkyrimInterop thread to exit
|
||||
inputQueue.Add(null);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
inputQueue.Add(input);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInputTerminated() {
|
||||
return isInputTerminated;
|
||||
}
|
||||
|
||||
public void WriteLine(string line) {
|
||||
inputQueue.Add(line);
|
||||
}
|
||||
|
||||
public string ReadLine() {
|
||||
return inputQueue.Take();
|
||||
}
|
||||
|
||||
public void RestoreSavedState() {
|
||||
if (currentFavoritesList != null) {
|
||||
inputQueue.Add(currentFavoritesList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
;;; Please do not edit, delete or move the next line (the ini section title), otherwise the options below will not take effect
|
||||
[Server]
|
||||
|
||||
;;; Specify the port to start the HTTP API on. Default is 12160
|
||||
;Port=12160
|
157
EmpathicQubit.ConsoleServer/EmpathicQbt.ConsoleServer.csproj
Normal file
157
EmpathicQubit.ConsoleServer/EmpathicQbt.ConsoleServer.csproj
Normal file
|
@ -0,0 +1,157 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<DragonBornModPath>SkyrimVR\Data\Plugins\Sumwunn</DragonBornModPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{DEA491EE-C426-4B79-A443-CF5B1D795288}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>EmpathicQbt.ConsoleServer</RootNamespace>
|
||||
<AssemblyName>EmpathicQbt.ConsoleServer</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<AllowedReferenceRelatedFileExtensions>-</AllowedReferenceRelatedFileExtensions>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\$(DragonBornModPath)\$(AssemblyName)\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\$(DragonBornModPath)\EmpathicQbt.ConsoleServer\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Costura, Version=2.0.0.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Costura.Fody.2.0.0\lib\net452\Costura.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="INIFileParser, Version=2.5.2.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Nancy, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Nancy.2.0.0\lib\net452\Nancy.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Nancy.Hosting.Self, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Nancy.Hosting.Self.2.0.0\lib\net452\Nancy.Hosting.Self.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="InputForwarder.cs" />
|
||||
<Compile Include="ServerBootstrapper.cs" />
|
||||
<Compile Include="ServerModule.cs" />
|
||||
<Compile Include="ConsoleInput.cs" />
|
||||
<Compile Include="Configuration.cs" />
|
||||
<Compile Include="ExternalInterop.cs" />
|
||||
<Compile Include="FavoritesList.cs" />
|
||||
<Compile Include="SkyrimInterop.cs" />
|
||||
<Compile Include="Log.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="FodyWeavers.xml" />
|
||||
<Content Include="static\index.css">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="static\index.html">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="static\index.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\packages\Fody.3.0.0\build\Fody.targets" Condition="Exists('..\packages\Fody.3.0.0\build\Fody.targets')" />
|
||||
<ItemGroup>
|
||||
<DragonBornZip Include="$(USERPROFILE)\Downloads\DragonbornSpeaksNaturally*.zip" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<FoMod Include="fomod\info.xml" />
|
||||
<FoMod Include="fomod\ModuleConfig.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<IniFile Include="DragonbornSpeaksNaturally.SAMPLE.ini" />
|
||||
</ItemGroup>
|
||||
<Target Name="ConcatIniFile">
|
||||
<!-- List all the files you want to concatenate. -->
|
||||
<ItemGroup>
|
||||
<ConcatFiles Include="
|
||||
$(ProjectDir)\$(OutputPath)\..\DragonbornSpeaksNaturally.SAMPLE.ini;
|
||||
$(ProjectDir)\DragonbornSpeaksNaturally.SAMPLE.ini"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Read the contents of the files (preserving tabs/spaces). -->
|
||||
<ItemGroup>
|
||||
<FileContents Include="$([System.IO.File]::ReadAllText(%(ConcatFiles.Identity)))"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Write the result to a single file. -->
|
||||
<WriteLinesToFile File="$(ProjectDir)\$(OutputPath)\..\DragonbornSpeaksNaturally.SAMPLE.ini" Lines="@(FileContents)" Overwrite="true" />
|
||||
</Target>
|
||||
<Target Name="CopyDragonbornNaturallySpeakingOriginal" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<DragonBornOutputs Include="$(ProjectDir)\$(OutputPath)\**\*.*" Visible="false" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<DragonBornShimGen><![CDATA[
|
||||
"$(SolutionDir)\packages\chocolatey.0.10.14\tools\chocolateyInstall\tools\shimgen.exe" --output="$(ProjectDir)\$(OutputPath)\..\DragonbornSpeaksNaturally.exe" -p="$(AssemblyName)\$(AssemblyName).exe"
|
||||
]]></DragonBornShimGen>
|
||||
<DragonBornPluginRoot>$(ProjectDir)\$(OutputPath)\..\..\..\..\..</DragonBornPluginRoot>
|
||||
<DragonBornSEOutputPath>$(DragonBornPluginRoot)\SkyrimSE\Data\Plugins\Sumwunn\$(AssemblyName)</DragonBornSEOutputPath>
|
||||
</PropertyGroup>
|
||||
<Unzip SourceFiles="@(DragonBornZip)" DestinationFolder="$(ProjectDir)\$(OutputPath)\..\..\..\..\.." />
|
||||
<Move SourceFiles="$(ProjectDir)\$(OutputPath)\..\DragonbornSpeaksNaturally.exe" DestinationFiles="$(ProjectDir)\$(OutputPath)\..\DragonbornSpeaksNaturally.Original.exe" />
|
||||
<Exec Command="$(DragonBornShimGen)" />
|
||||
<CallTarget Targets="ConcatIniFile" />
|
||||
<Copy SkipUnchangedFiles="true" SourceFiles="@(DragonBornOutputs)" DestinationFolder="$(DragonBornSEOutputPath)" />
|
||||
<Copy SkipUnchangedFiles="true" SourceFiles="$(ProjectDir)\$(OutputPath)\..\DragonbornSpeaksNaturally.exe" DestinationFolder="$(DragonBornSEOutputPath)\.." />
|
||||
<Copy SkipUnchangedFiles="true" SourceFiles="$(ProjectDir)\$(OutputPath)\..\DragonbornSpeaksNaturally.Original.exe" DestinationFolder="$(DragonBornSEOutputPath)\.." />
|
||||
<Copy SkipUnchangedFiles="true" SourceFiles="$(ProjectDir)\$(OutputPath)\..\DragonbornSpeaksNaturally.SAMPLE.ini" DestinationFolder="$(DragonBornSEOutputPath)\.." />
|
||||
<Copy SkipUnchangedFiles="true" SourceFiles="@(FoMod)" DestinationFolder="$(DragonBornPluginRoot)\fomod" />
|
||||
</Target>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Fody.3.0.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.3.0.0\build\Fody.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Costura.Fody.2.0.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.2.0.0\build\Costura.Fody.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Costura.Fody.2.0.0\build\Costura.Fody.targets" Condition="Exists('..\packages\Costura.Fody.2.0.0\build\Costura.Fody.targets')" />
|
||||
</Project>
|
87
EmpathicQubit.ConsoleServer/ExternalInterop.cs
Normal file
87
EmpathicQubit.ConsoleServer/ExternalInterop.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer {
|
||||
public class ExternalInterop {
|
||||
|
||||
private Configuration config = null;
|
||||
private SkyrimInterop skyrimInterop = null;
|
||||
|
||||
private readonly HashSet<string> BATCH_FILENAMES = new HashSet<string>() { "wotv", "ivrqs" };
|
||||
private readonly long FILE_CHANGE_DEBOUNCE_TIME_TICKS = 10000 * 200; // 200 ms
|
||||
|
||||
private string configFileName = null;
|
||||
private FileSystemWatcher configFileWatcher;
|
||||
private bool isConfigFileChanged = false;
|
||||
|
||||
|
||||
public ExternalInterop(Configuration config, SkyrimInterop skyrimInterop) {
|
||||
this.config = config;
|
||||
this.skyrimInterop = skyrimInterop;
|
||||
}
|
||||
|
||||
public void Start() {
|
||||
ListenForConfigFile();
|
||||
}
|
||||
|
||||
public void Stop() {
|
||||
if(configFileWatcher != null)
|
||||
{
|
||||
configFileWatcher.EnableRaisingEvents = false;
|
||||
}
|
||||
}
|
||||
private void ListenForConfigFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
string filePath = config.GetIniFilePath();
|
||||
if (filePath == null) {
|
||||
throw new Exception("Configuration file does not exist.");
|
||||
}
|
||||
|
||||
configFileName = Path.GetFileName(filePath).ToLower();
|
||||
|
||||
configFileWatcher = new FileSystemWatcher();
|
||||
configFileWatcher.Path = Path.GetDirectoryName(filePath);
|
||||
configFileWatcher.Changed += Watcher_ConfigFileChanged;
|
||||
configFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime;
|
||||
Trace.TraceInformation("Watching for config file at {0}", configFileWatcher.Path);
|
||||
configFileWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceError("Failed to watch for config files: {0}", ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void Watcher_ConfigFileChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
if (!isConfigFileChanged &&
|
||||
(e.ChangeType == WatcherChangeTypes.Changed || e.ChangeType == WatcherChangeTypes.Created))
|
||||
{
|
||||
string filename = e.Name.ToLower();
|
||||
if (filename.Equals(configFileName))
|
||||
{
|
||||
Trace.TraceInformation("Config file {0} changed", filename);
|
||||
|
||||
// Wait for the configuration file to be saved
|
||||
Thread.Sleep(1000);
|
||||
|
||||
isConfigFileChanged = true;
|
||||
Stop();
|
||||
skyrimInterop.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConfigFileChanged() {
|
||||
return isConfigFileChanged;
|
||||
}
|
||||
}
|
||||
}
|
173
EmpathicQubit.ConsoleServer/FavoritesList.cs
Normal file
173
EmpathicQubit.ConsoleServer/FavoritesList.cs
Normal file
|
@ -0,0 +1,173 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Script.Serialization;
|
||||
using System.IO;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer {
|
||||
public class Favorite
|
||||
{
|
||||
public string ItemName { get; set; }
|
||||
public long FormId { get; set; }
|
||||
public long ItemId { get; set; }
|
||||
public bool IsSingleHanded { get; set; }
|
||||
public int TypeId { get; set; }
|
||||
}
|
||||
|
||||
public class FavoritesList {
|
||||
|
||||
private Configuration config;
|
||||
|
||||
public IList<Favorite> Favorites { get; protected set; } = new List<Favorite>()
|
||||
{
|
||||
new Favorite
|
||||
{
|
||||
ItemId = 666,
|
||||
TypeId = 999,
|
||||
FormId = 222,
|
||||
IsSingleHanded = true,
|
||||
ItemName = "Wabbajack",
|
||||
},
|
||||
new Favorite
|
||||
{
|
||||
ItemId = 666,
|
||||
TypeId = 999,
|
||||
FormId = 222,
|
||||
IsSingleHanded = true,
|
||||
ItemName = "Wabbajack2",
|
||||
},
|
||||
new Favorite
|
||||
{
|
||||
ItemId = 666,
|
||||
TypeId = 999,
|
||||
FormId = 222,
|
||||
IsSingleHanded = true,
|
||||
ItemName = "Wabbajack3",
|
||||
},
|
||||
new Favorite
|
||||
{
|
||||
ItemId = 666,
|
||||
TypeId = 999,
|
||||
FormId = 222,
|
||||
IsSingleHanded = true,
|
||||
ItemName = "Wabbajack4",
|
||||
},
|
||||
new Favorite
|
||||
{
|
||||
ItemId = 666,
|
||||
TypeId = 999,
|
||||
FormId = 222,
|
||||
IsSingleHanded = true,
|
||||
ItemName = "Wabbajack5",
|
||||
},
|
||||
new Favorite
|
||||
{
|
||||
ItemId = 666,
|
||||
TypeId = 999,
|
||||
FormId = 222,
|
||||
IsSingleHanded = true,
|
||||
ItemName = "Wabbajack6",
|
||||
},
|
||||
new Favorite
|
||||
{
|
||||
ItemId = 666,
|
||||
TypeId = 999,
|
||||
FormId = 222,
|
||||
IsSingleHanded = true,
|
||||
ItemName = "Wabbajack7",
|
||||
},
|
||||
new Favorite
|
||||
{
|
||||
ItemId = 666,
|
||||
TypeId = 999,
|
||||
FormId = 222,
|
||||
IsSingleHanded = true,
|
||||
ItemName = "Wabbajack8",
|
||||
},
|
||||
};
|
||||
|
||||
public FavoritesList(Configuration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// Locates and loads item name replacement maps
|
||||
// Returns dynamic map/dictionary or null when the replacement map files cannot be located
|
||||
public dynamic LoadItemNameMap()
|
||||
{
|
||||
string filepath = Configuration.ResolveFilePath("item-name-map.json");
|
||||
if(File.Exists(filepath))
|
||||
{
|
||||
return LoadItemNameMap(filepath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Returns a map/dictionary or throws exception when the file cannot be opened/read
|
||||
public dynamic LoadItemNameMap(string path)
|
||||
{
|
||||
var json = System.IO.File.ReadAllText(path);
|
||||
JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
|
||||
return jsonSerializer.Deserialize<dynamic>(json);
|
||||
}
|
||||
|
||||
public string MaybeReplaceItemName(dynamic nameMap, string itemName)
|
||||
{
|
||||
if (nameMap == null)
|
||||
return itemName;
|
||||
|
||||
try
|
||||
{
|
||||
return nameMap[itemName];
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return itemName;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(string input) {
|
||||
dynamic itemNameMap = LoadItemNameMap();
|
||||
|
||||
Favorites.Clear();
|
||||
string[] itemTokens = input.Split('|');
|
||||
foreach(string itemStr in itemTokens) {
|
||||
try
|
||||
{
|
||||
string[] tokens = itemStr.Split(',');
|
||||
string itemName = tokens[0];
|
||||
long formId = long.Parse(tokens[1]);
|
||||
long itemId = long.Parse(tokens[2]);
|
||||
bool isSingleHanded = int.Parse(tokens[3]) > 0;
|
||||
int typeId = int.Parse(tokens[4]);
|
||||
|
||||
Favorites.Add(new Favorite
|
||||
{
|
||||
FormId = formId,
|
||||
ItemName = itemName,
|
||||
ItemId = itemId,
|
||||
IsSingleHanded = isSingleHanded,
|
||||
TypeId = typeId,
|
||||
});
|
||||
|
||||
itemName = MaybeReplaceItemName(itemNameMap, itemName);
|
||||
|
||||
// FIXME Store item info
|
||||
} catch(Exception ex) {
|
||||
Trace.TraceError("Failed to parse {0} due to exception:\n{1}", itemStr, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
PrintToTrace();
|
||||
}
|
||||
|
||||
public void PrintToTrace() {
|
||||
Trace.TraceInformation("Favorites List:");
|
||||
foreach (var favorite in Favorites) {
|
||||
Trace.TraceInformation("{0}: {1}: {2}", favorite.ItemName, favorite.TypeId, favorite.IsSingleHanded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
EmpathicQubit.ConsoleServer/FodyWeavers.xml
Normal file
4
EmpathicQubit.ConsoleServer/FodyWeavers.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers>
|
||||
<!--<Costura />-->
|
||||
</Weavers>
|
87
EmpathicQubit.ConsoleServer/InputForwarder.cs
Normal file
87
EmpathicQubit.ConsoleServer/InputForwarder.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer
|
||||
{
|
||||
public class InputForwarder
|
||||
{
|
||||
private BlockingCollection<string> outputQueue = new BlockingCollection<string>();
|
||||
private Thread outputThread = null;
|
||||
private StreamWriter inputWriter = null;
|
||||
private Process subProcess = null;
|
||||
bool isOutputTerminated = false;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
outputThread = new Thread(ReadLineFromProcess);
|
||||
var filename = Configuration.ResolveFilePath("DragonbornSpeaksNaturally.Original.exe");
|
||||
|
||||
Trace.TraceInformation("Starting {0}", filename);
|
||||
subProcess = Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = filename,
|
||||
Arguments = "\"" + String.Join("\" \"", System.Environment.GetCommandLineArgs().Skip(1)) + "\"",
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
StandardOutputEncoding = Console.OutputEncoding,
|
||||
StandardErrorEncoding = Console.OutputEncoding,
|
||||
});
|
||||
subProcess.Start();
|
||||
inputWriter = subProcess.StandardInput;
|
||||
inputWriter.AutoFlush = true;
|
||||
outputThread.Start();
|
||||
}
|
||||
|
||||
private void ReadLineFromProcess()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
string input = subProcess.StandardOutput.ReadLine();
|
||||
|
||||
// input will be null when Skyrim terminated (stdin closed)
|
||||
if (input == null)
|
||||
{
|
||||
isOutputTerminated = true;
|
||||
Trace.TraceInformation("Skyrim is terminated, console server will quit.");
|
||||
|
||||
// Notify the SkyrimInterop thread to exit
|
||||
outputQueue.Add(null);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
outputQueue.Add(input);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInputTerminated() {
|
||||
return isOutputTerminated;
|
||||
}
|
||||
|
||||
public void WriteLine(string line) {
|
||||
inputWriter.WriteLine(line);
|
||||
inputWriter.Flush();
|
||||
inputWriter.BaseStream.Flush();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if(subProcess != null)
|
||||
{
|
||||
subProcess.Kill();
|
||||
}
|
||||
}
|
||||
|
||||
public string ReadLine() {
|
||||
return outputQueue.Take();
|
||||
}
|
||||
}
|
||||
}
|
31
EmpathicQubit.ConsoleServer/Log.cs
Normal file
31
EmpathicQubit.ConsoleServer/Log.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer {
|
||||
public class Log {
|
||||
|
||||
private static readonly string ERROR_LOG_FILE = "EmpathicQbt.ConsoleServer.log";
|
||||
|
||||
public static void Initialize() {
|
||||
string logFilePath = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
logFilePath += "\\EmpathicQbt.ConsoleServer";
|
||||
System.IO.Directory.CreateDirectory(logFilePath);
|
||||
logFilePath += "\\"+ERROR_LOG_FILE;
|
||||
try {
|
||||
// The compiler constant TRACE needs to be defined, otherwise logs will not be output to the file.
|
||||
var listener = new TextWriterTraceListener(logFilePath);
|
||||
listener.TraceOutputOptions = TraceOptions.DateTime;
|
||||
Trace.AutoFlush = true;
|
||||
Trace.Listeners.Add(listener);
|
||||
}
|
||||
catch(Exception ex) {
|
||||
Console.Error.WriteLine("Failed to create log file at " + logFilePath + ": {0}", ex.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
77
EmpathicQubit.ConsoleServer/Program.cs
Normal file
77
EmpathicQubit.ConsoleServer/Program.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Nancy.Hosting.Self;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer {
|
||||
class Program {
|
||||
private static readonly string VERSION = "0.19";
|
||||
|
||||
static void Main(string[] args) {
|
||||
try
|
||||
{
|
||||
Log.Initialize();
|
||||
Trace.TraceInformation("ConsoleServer service started", VERSION);
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (args[i].Equals("--encoding") && args.Length >= i + 1)
|
||||
{
|
||||
string encode = args[i+1];
|
||||
|
||||
// Set encoding of stdin/stdout to the client specified.
|
||||
// This can avoid non-ASCII characters (such as Chinese characters) garbled.
|
||||
Console.InputEncoding = System.Text.Encoding.GetEncoding(encode);
|
||||
Console.OutputEncoding = System.Text.Encoding.GetEncoding(encode);
|
||||
|
||||
Trace.TraceInformation("Set encoding of stdin/stdout to {0}", encode);
|
||||
}
|
||||
}
|
||||
|
||||
// Thread.Abort() cannot abort the calling of Console.ReadLine().
|
||||
// So the call is in a separate thread that does not need to be restarted
|
||||
// after reloading the configuration file.
|
||||
ConsoleInput consoleInput = new ConsoleInput();
|
||||
consoleInput.Start();
|
||||
|
||||
var inputForwarder = new InputForwarder();
|
||||
inputForwarder.Start();
|
||||
|
||||
bool reloadConfigFile = true;
|
||||
while (reloadConfigFile)
|
||||
{
|
||||
Configuration config = new Configuration();
|
||||
SkyrimInterop skyrimInterop = new SkyrimInterop(config, consoleInput, inputForwarder);
|
||||
ExternalInterop externalInterop = new ExternalInterop(config, skyrimInterop);
|
||||
|
||||
var port = config.Get("Server", "Port", "12160");
|
||||
var host = new NancyHost(new ServerBootstrapper(skyrimInterop), new HostConfiguration()
|
||||
{
|
||||
RewriteLocalhost = false,
|
||||
}, new Uri($"http://localhost:{port}"));
|
||||
host.Start();
|
||||
|
||||
skyrimInterop.Start();
|
||||
externalInterop.Start();
|
||||
|
||||
// skyrimThread will terminate when Skyrim terminated (stdin closed) or config file updated
|
||||
skyrimInterop.Join();
|
||||
|
||||
reloadConfigFile = externalInterop.IsConfigFileChanged();
|
||||
|
||||
if (!reloadConfigFile)
|
||||
{
|
||||
// Cleanup threads
|
||||
externalInterop.Stop();
|
||||
skyrimInterop.Stop();
|
||||
inputForwarder.Stop();
|
||||
host.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
Trace.TraceError(ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
EmpathicQubit.ConsoleServer/Properties/AssemblyInfo.cs
Normal file
36
EmpathicQubit.ConsoleServer/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("EmpathicQbt.ConsoleServer")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("EmpathicQbt.ConsoleServer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("dea491ee-c426-4b79-a443-cf5b1d795288")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
23
EmpathicQubit.ConsoleServer/ServerBootstrapper.cs
Normal file
23
EmpathicQubit.ConsoleServer/ServerBootstrapper.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Nancy;
|
||||
using Nancy.TinyIoc;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer
|
||||
{
|
||||
public class ServerBootstrapper : DefaultNancyBootstrapper
|
||||
{
|
||||
private SkyrimInterop skyrimInterop;
|
||||
public ServerBootstrapper(SkyrimInterop skyrimInterop)
|
||||
{
|
||||
this.skyrimInterop = skyrimInterop;
|
||||
}
|
||||
|
||||
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
|
||||
{
|
||||
base.ConfigureRequestContainer(container, context);
|
||||
|
||||
container.Register<SkyrimInterop>(skyrimInterop);
|
||||
}
|
||||
}
|
||||
}
|
43
EmpathicQubit.ConsoleServer/ServerModule.cs
Normal file
43
EmpathicQubit.ConsoleServer/ServerModule.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using IniParser;
|
||||
using IniParser.Model;
|
||||
using Nancy;
|
||||
using Nancy.ModelBinding;
|
||||
using Nancy.Responses;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer {
|
||||
public class CommandRequest
|
||||
{
|
||||
public string Command { get; set; }
|
||||
}
|
||||
|
||||
public class ServerModule : NancyModule {
|
||||
private SkyrimInterop skyrimInterop;
|
||||
|
||||
public ServerModule(SkyrimInterop skyrimInterop)
|
||||
{
|
||||
this.skyrimInterop = skyrimInterop;
|
||||
Get("/api/ping", _ => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
|
||||
Get("/api/favorites", GetFavorites);
|
||||
Post("/api/command", PostCommand);
|
||||
Get("/{filename}", _ => Response.AsFile("static/" + (string)_.filename));
|
||||
}
|
||||
|
||||
public object PostCommand(dynamic x) {
|
||||
var cmd = this.Bind<CommandRequest>();
|
||||
skyrimInterop.SubmitCommand("COMMAND|" + cmd.Command);
|
||||
return Response.AsJson(new { Status = "OK" }, HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
public object GetFavorites(dynamic x) {
|
||||
return Response.AsJson(skyrimInterop.GetFavorites(), HttpStatusCode.OK);
|
||||
}
|
||||
}
|
||||
}
|
130
EmpathicQubit.ConsoleServer/SkyrimInterop.cs
Normal file
130
EmpathicQubit.ConsoleServer/SkyrimInterop.cs
Normal file
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmpathicQbt.ConsoleServer {
|
||||
public class SkyrimInterop {
|
||||
|
||||
private Configuration config = null;
|
||||
private ConsoleInput consoleInput = null;
|
||||
private InputForwarder inputForwarder = null;
|
||||
private FavoritesList favoritesList = null;
|
||||
private Thread submissionThread;
|
||||
private Thread listenThread;
|
||||
private Thread forwardThread;
|
||||
private BlockingCollection<string> commandQueue;
|
||||
|
||||
public SkyrimInterop(Configuration config, ConsoleInput consoleInput, InputForwarder inputForwarder) {
|
||||
this.config = config;
|
||||
this.consoleInput = consoleInput;
|
||||
this.inputForwarder = inputForwarder;
|
||||
}
|
||||
|
||||
public void Start() {
|
||||
try {
|
||||
favoritesList = new FavoritesList(config);
|
||||
commandQueue = new BlockingCollection<string>();
|
||||
|
||||
listenThread = new Thread(ListenForInput);
|
||||
submissionThread = new Thread(SubmitCommands);
|
||||
forwardThread = new Thread(ListenForForward);
|
||||
submissionThread.Start();
|
||||
listenThread.Start();
|
||||
forwardThread.Start();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Trace.TraceError("Failed to initialize due to error:");
|
||||
Trace.TraceError(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public void Join() {
|
||||
listenThread.Join();
|
||||
}
|
||||
|
||||
public void Stop() {
|
||||
// Notify threads to exit
|
||||
consoleInput.WriteLine(null);
|
||||
inputForwarder.WriteLine(null);
|
||||
commandQueue.Add(null);
|
||||
}
|
||||
|
||||
public void SubmitCommand(string command) {
|
||||
commandQueue.Add(sanitize(command));
|
||||
}
|
||||
|
||||
private static string sanitize(string command) {
|
||||
command = command.Trim();
|
||||
return command.Replace("\r", "");
|
||||
}
|
||||
|
||||
private void SubmitCommands() {
|
||||
while (true) {
|
||||
string command = commandQueue.Take();
|
||||
|
||||
// Thread exit signal
|
||||
if (command == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
Trace.TraceInformation("Sending command: {0}", command);
|
||||
Console.Write(command + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
public IList<Favorite> GetFavorites() => favoritesList.Favorites;
|
||||
|
||||
private void ListenForForward() {
|
||||
try {
|
||||
while (true) {
|
||||
string input = inputForwarder.ReadLine();
|
||||
|
||||
// input will be null when Skyrim terminated (stdin closed)
|
||||
if (input == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
Trace.TraceInformation("Received command to forward: {0}", input);
|
||||
|
||||
SubmitCommand(input);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Trace.TraceError(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void ListenForInput() {
|
||||
try {
|
||||
// try to restore saved state after reloading the configuration file.
|
||||
consoleInput.RestoreSavedState();
|
||||
|
||||
while (true) {
|
||||
string input = consoleInput.ReadLine();
|
||||
|
||||
// input will be null when Skyrim terminated (stdin closed)
|
||||
if (input == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
Trace.TraceInformation("Received command: {0}", input);
|
||||
|
||||
inputForwarder.WriteLine(input);
|
||||
|
||||
string[] tokens = input.Split('|');
|
||||
string command = tokens[0];
|
||||
if (command.Equals("FAVORITES")) {
|
||||
consoleInput.currentFavoritesList = input;
|
||||
favoritesList.Update(string.Join("|", tokens, 1, tokens.Length - 1));
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Trace.TraceError(ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
EmpathicQubit.ConsoleServer/fomod/ModuleConfig.xml
Normal file
72
EmpathicQubit.ConsoleServer/fomod/ModuleConfig.xml
Normal file
|
@ -0,0 +1,72 @@
|
|||
<!-- FOMod Creation Tool [http://www.nexusmods.com/fallout4/mods/6821] -->
|
||||
<!-- FOMod Quick Guide [https://media.readthedocs.org/pdf/fomod-docs/latest/fomod-docs.pdf] -->
|
||||
<!-- FOMod Reference Manual [https://fomod-designer.readthedocs.io/en/stable/] -->
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
|
||||
<moduleName>Dragonborn Speaks Naturally + Weapon Select UI and HTTP API</moduleName>
|
||||
<requiredInstallFiles>
|
||||
<folder source="docs" destination="Docs" priority="0"/>
|
||||
</requiredInstallFiles>
|
||||
<installSteps order="Explicit">
|
||||
<installStep name="Choose Option">
|
||||
<optionalFileGroups order="Explicit">
|
||||
<group name="Skyrim Edition:" type="SelectExactlyOne">
|
||||
<plugins order="Explicit">
|
||||
<plugin name="Skyrim VR">
|
||||
<description><![CDATA[This plugin is compatible and only compatible with SkyrimVR 1.4.15.0.
|
||||
|
||||
To make the plugin working, you must manually install xSHADOWMANx's Dll Loader to the game root directory.
|
||||
|
||||
You cannot install the loader via any MOD manager (But DSN can be installed via MOD Manager).
|
||||
|
||||
You can find the loader here: https://www.nexusmods.com/skyrimspecialedition/mods/3619]]></description>
|
||||
<files>
|
||||
<folder source="SkyrimVR\Data" destination="" priority="0"/>
|
||||
<file source="fomod/DSN-installed.md" destination="SKSE/DSN/DSN-for-SkyrimVR-installed.md" priority="0"/>
|
||||
</files>
|
||||
<typeDescriptor>
|
||||
<dependencyType>
|
||||
<defaultType name="Optional"/>
|
||||
<patterns>
|
||||
<pattern>
|
||||
<dependencies operator="Or">
|
||||
<fileDependency file="SkyrimVR.esp" state="Active"/>
|
||||
<fileDependency file="SkyrimVR.esp" state="Inactive"/>
|
||||
</dependencies>
|
||||
<type name="Recommended"/>
|
||||
</pattern>
|
||||
</patterns>
|
||||
</dependencyType>
|
||||
</typeDescriptor>
|
||||
</plugin>
|
||||
<plugin name="Skyrim Special Edition">
|
||||
<description><![CDATA[This plugin is compatible and only compatible with Skyrim Special Edition 1.5.97.0.
|
||||
|
||||
To make the plugin working, you must manually install xSHADOWMANx's Dll Loader to the game root directory.
|
||||
|
||||
You cannot install the loader via any MOD manager (But DSN can be installed via MOD Manager).
|
||||
|
||||
You can find the loader here: https://www.nexusmods.com/skyrimspecialedition/mods/3619]]></description>
|
||||
<files>
|
||||
<folder source="SkyrimSE\Data" destination="" priority="0"/>
|
||||
<file source="fomod/DSN-installed.md" destination="SKSE/DSN/DSN-for-SkyrimSE-installed.md" priority="0"/>
|
||||
</files>
|
||||
<typeDescriptor>
|
||||
<dependencyType>
|
||||
<defaultType name="Optional"/>
|
||||
<patterns>
|
||||
<pattern>
|
||||
<dependencies operator="Or">
|
||||
<fileDependency file="SkyrimVR.esm" state="Missing"/>
|
||||
</dependencies>
|
||||
<type name="Recommended"/>
|
||||
</pattern>
|
||||
</patterns>
|
||||
</dependencyType>
|
||||
</typeDescriptor>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</group>
|
||||
</optionalFileGroups>
|
||||
</installStep>
|
||||
</installSteps>
|
||||
</config>
|
6
EmpathicQubit.ConsoleServer/fomod/info.xml
Normal file
6
EmpathicQubit.ConsoleServer/fomod/info.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<!-- Created with FOMOD Creation Tool 1.7.0.37 [http://www.nexusmods.com/fallout4/mods/6821] -->
|
||||
<!-- FOMod Document [https://media.readthedocs.org/pdf/fomod-docs/latest/fomod-docs.pdf] -->
|
||||
<fomod>
|
||||
<Name>Dragonborn Speaks Naturally + Weapon Select UI and HTTP API</Name>
|
||||
<Website>https://www.nexusmods.com/skyrimspecialedition/mods/FIXME</Website>
|
||||
</fomod>
|
11
EmpathicQubit.ConsoleServer/packages.config
Normal file
11
EmpathicQubit.ConsoleServer/packages.config
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="chocolatey" version="0.10.14" targetFramework="net461" />
|
||||
<package id="Costura.Fody" version="2.0.0" targetFramework="net461" />
|
||||
<package id="Fody" version="3.0.0" targetFramework="net461" developmentDependency="true" />
|
||||
<package id="ini-parser" version="2.5.2" targetFramework="net461" />
|
||||
<package id="KeystrokeAPI" version="1.0.6.4" targetFramework="net461" />
|
||||
<package id="Nancy" version="2.0.0" targetFramework="net461" />
|
||||
<package id="Nancy.Hosting.Self" version="2.0.0" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
|
||||
</packages>
|
86
EmpathicQubit.ConsoleServer/static/index.css
Normal file
86
EmpathicQubit.ConsoleServer/static/index.css
Normal file
|
@ -0,0 +1,86 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: sans-serif;
|
||||
font-size: 4.3vh;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-shadow: rgb(0, 0, 0) 3px 0px 0px, rgb(0, 0, 0) 2.83487px 0.981584px 0px, rgb(0, 0, 0) 2.35766px 1.85511px 0px, rgb(0, 0, 0) 1.62091px 2.52441px 0px, rgb(0, 0, 0) 0.705713px 2.91581px 0px, rgb(0, 0, 0) -0.287171px 2.98622px 0px, rgb(0, 0, 0) -1.24844px 2.72789px 0px, rgb(0, 0, 0) -2.07227px 2.16926px 0px, rgb(0, 0, 0) -2.66798px 1.37182px 0px, rgb(0, 0, 0) -2.96998px 0.42336px 0px, rgb(0, 0, 0) -2.94502px -0.571704px 0px, rgb(0, 0, 0) -2.59586px -1.50383px 0px, rgb(0, 0, 0) -1.96093px -2.27041px 0px, rgb(0, 0, 0) -1.11013px -2.78704px 0px, rgb(0, 0, 0) -0.137119px -2.99686px 0px, rgb(0, 0, 0) 0.850987px -2.87677px 0px, rgb(0, 0, 0) 1.74541px -2.43999px 0px, rgb(0, 0, 0) 2.44769px -1.73459px 0px, rgb(0, 0, 0) 2.88051px -0.838247px 0px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
background-color: #0f0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.2em;
|
||||
margin: .5em 0;
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.favorites {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.favorites li {
|
||||
display: block;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.favorites ul {
|
||||
width: 18.1em;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.favorites h1 {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.favorites button {
|
||||
word-wrap: break-word;
|
||||
width: 5.5em;
|
||||
height: 5.5em;
|
||||
margin: .25em;
|
||||
border: .15em solid black;
|
||||
border-radius: .25em;
|
||||
opacity: 0.8;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.favorites__left ul {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.favorites__right ul {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.favorites__left button {
|
||||
background-color: #800;
|
||||
}
|
||||
|
||||
.favorites button:hover {
|
||||
border-color: #909;
|
||||
text-shadow: #909 3px 0px 0px, #909 2.83487px 0.981584px 0px, #909 2.35766px 1.85511px 0px, #909 1.62091px 2.52441px 0px, #909 0.705713px 2.91581px 0px, #909 -0.287171px 2.98622px 0px, #909 -1.24844px 2.72789px 0px, #909 -2.07227px 2.16926px 0px, #909 -2.66798px 1.37182px 0px, #909 -2.96998px 0.42336px 0px, #909 -2.94502px -0.571704px 0px, #909 -2.59586px -1.50383px 0px, #909 -1.96093px -2.27041px 0px, #909 -1.11013px -2.78704px 0px, #909 -0.137119px -2.99686px 0px, #909 0.850987px -2.87677px 0px, #909 1.74541px -2.43999px 0px, #909 2.44769px -1.73459px 0px, #909 2.88051px -0.838247px 0px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.favorites__left button:hover {
|
||||
background-color: #70d;
|
||||
}
|
||||
|
||||
.favorites__right button {
|
||||
background-color: #008;
|
||||
}
|
||||
|
||||
.favorites__right button:hover {
|
||||
background-color: #e4005f;
|
||||
}
|
11
EmpathicQubit.ConsoleServer/static/index.html
Normal file
11
EmpathicQubit.ConsoleServer/static/index.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script type="module">
|
||||
import './index.js'
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
112
EmpathicQubit.ConsoleServer/static/index.js
Normal file
112
EmpathicQubit.ConsoleServer/static/index.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
const state = {
|
||||
favorites: [],
|
||||
}
|
||||
|
||||
const processItem = (parent, item) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.nodeType !== undefined) {
|
||||
parent.appendChild(item);
|
||||
}
|
||||
else if (typeof item != "object") {
|
||||
const elem = document.createTextNode(item);
|
||||
parent.appendChild(elem);
|
||||
}
|
||||
else {
|
||||
for (const kid of item) {
|
||||
processItem(parent, kid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const e = (name, attrs, kids) => {
|
||||
const elem = document.createElement(name);
|
||||
for (const a in attrs) {
|
||||
if (a == 'style' || a == 'dataset') {
|
||||
for (const s in attrs[a]) {
|
||||
elem[a][s] = attrs[a][s];
|
||||
}
|
||||
}
|
||||
else {
|
||||
elem[a] = attrs[a];
|
||||
}
|
||||
}
|
||||
|
||||
processItem(elem, kids);
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
const equip = async (item, side) => {
|
||||
side = side || 'right'
|
||||
const res = await fetch('/api/command', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ command: `player.equipitem ${item.formId.toString(16).padStart(8, '0')} 0 ${side}` }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
});
|
||||
const status = await res.json()
|
||||
};
|
||||
|
||||
const reloadFavorites = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/favorites', { headers: { 'Accept': 'application/json' } });
|
||||
const favorites = await res.json()
|
||||
let different = false;
|
||||
for (const f in favorites) {
|
||||
const favorite = favorites[f];
|
||||
const orig = state.favorites[f];
|
||||
if (!orig || orig.formId != favorite.formId) {
|
||||
different = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!different) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.favorites = favorites;
|
||||
render();
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
setTimeout(reloadFavorites, UPDATE_INTERVAL)
|
||||
};
|
||||
|
||||
const render = async () => {
|
||||
try {
|
||||
const content = e('div', { id: 'content' }, [
|
||||
e('div', { className: 'favorites favorites__left' }, [
|
||||
e('h1', null, 'Left Hand'),
|
||||
e('ul', null, state.favorites.map(item =>
|
||||
e('li', null,
|
||||
e('button', { onclick: () => equip(item, 'left') }, item.itemName),
|
||||
),
|
||||
)),
|
||||
]),
|
||||
e('div', { className: 'favorites favorites__right' }, [
|
||||
e('h1', null, 'Right Hand'),
|
||||
e('ul', null, state.favorites.map(item =>
|
||||
e('li', null,
|
||||
e('button', { onclick: () => equip(item, 'right') }, item.itemName),
|
||||
),
|
||||
)),
|
||||
]),
|
||||
]);
|
||||
document.getElementById("content").replaceWith(content);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(reloadFavorites, UPDATE_INTERVAL)
|
11
README.md
Normal file
11
README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# EmpathicQbt.ConsoleServer
|
||||
|
||||
A proxy that intercepts Dragonborn Speaks Naturally to provide a weapon select interface.
|
||||
|
||||
## Building
|
||||
|
||||
1. Download the [latest DSN ZIP](https://www.nexusmods.com/skyrimspecialedition/mods/16514)
|
||||
to your Downloads folder.
|
||||
2. Build it
|
||||
3. Copy all files in the `bin/$(Configuration)` folder into a ZIP.
|
||||
4. Install in Vortex or NMM
|
84
configure.bat
Normal file
84
configure.bat
Normal file
|
@ -0,0 +1,84 @@
|
|||
@echo off & setlocal ENABLEDELAYEDEXPANSION
|
||||
|
||||
:: jump to script's parent dir
|
||||
cd /d %~dp0
|
||||
|
||||
:: load/create option files
|
||||
SET InstallPathConfig=install-path.ini
|
||||
|
||||
:: read or generate config files
|
||||
if exist %InstallPathConfig% (
|
||||
call :load_ini %InstallPathConfig%
|
||||
call :get_ini SkyrimVR InstallPath SkyrimVRInstallPath
|
||||
call :get_ini SkyrimSE InstallPath SkyrimSEInstallPath
|
||||
) else (
|
||||
echo Set plugin install directories after building:
|
||||
set /p SkyrimVRInstallPath="SkyrimVR game root path (empty to disable installation): "
|
||||
set /p SkyrimSEInstallPath="SkyrimSE game root path (empty to disable installation): "
|
||||
|
||||
call :set_ini SkyrimVR InstallPath "!SkyrimVRInstallPath!"
|
||||
call :set_ini SkyrimSE InstallPath "!SkyrimSEInstallPath!"
|
||||
call :save_ini >%InstallPathConfig%
|
||||
)
|
||||
|
||||
:: create and enter build dir
|
||||
md build
|
||||
cd build
|
||||
|
||||
:: run CMake
|
||||
set CMakeFlags=
|
||||
if defined SkyrimVRInstallPath (
|
||||
set CMakeFlags=!CMakeFlags! -DSVR_DIR="!SkyrimVRInstallPath!"
|
||||
)
|
||||
if defined SkyrimSEInstallPath (
|
||||
set CMakeFlags=!CMakeFlags! -DSSE_DIR="!SkyrimSEInstallPath!"
|
||||
)
|
||||
echo CMakeFlags:!CMakeFlags!
|
||||
|
||||
cmake -A x64 !CMakeFlags! ..
|
||||
|
||||
:: disable Prefer32Bit for C# project
|
||||
type ..\disable_prefer_32bit.ps1 | powershell.exe -Command -
|
||||
|
||||
:: load project
|
||||
start dsn_service.sln
|
||||
|
||||
:: pause for user's lookup
|
||||
pause
|
||||
goto :eof
|
||||
|
||||
|
||||
:: ----------------- ini parse & edit functions -----------------
|
||||
:: From <https://zhidao.baidu.com/question/982407720655882739.html>
|
||||
|
||||
:load_ini [param#1=ini file path]
|
||||
set "op="
|
||||
for /f " usebackq tokens=1* delims==" %%a in ("%~1") do (
|
||||
if "%%b"=="" (
|
||||
set "op=%%a"
|
||||
) else (
|
||||
set "##!op!#%%a=%%b"
|
||||
)
|
||||
)
|
||||
goto :eof
|
||||
|
||||
:get_ini [param#1=Option] [param#2=Key] [param#3=StoredVar]
|
||||
set %~3=!##[%~1]#%~2!
|
||||
goto :eof
|
||||
|
||||
:set_ini [param#1=Option] [param#2=Key] [param#3=Value, without it the key will be deleted]
|
||||
set "##[%~1]#%~2=%~3"
|
||||
goto :eof
|
||||
|
||||
:save_ini [>ini path]
|
||||
set "op="
|
||||
set "##=##"
|
||||
for /f "tokens=1-3 delims=#=" %%a in ('set ##') do (
|
||||
if "%%a"=="!op!" (
|
||||
echo,%%b=%%c
|
||||
) else (
|
||||
echo,%%a
|
||||
set "op=%%a"
|
||||
echo,%%b=%%c
|
||||
)
|
||||
)
|
26
disable_prefer_32bit.ps1
Normal file
26
disable_prefer_32bit.ps1
Normal file
|
@ -0,0 +1,26 @@
|
|||
# add Prefer32Bit=false to a csproj file
|
||||
|
||||
$file = 'dsn_service.csproj'
|
||||
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
$doc.Load($file)
|
||||
|
||||
$pgroups = $doc.DocumentElement.PropertyGroup
|
||||
$pgroupCount = 0
|
||||
|
||||
for ($i=0; $i -le $pgroups.Count; $i++) {
|
||||
if ($pgroups[$i].PlatformTarget -eq "anycpu") {
|
||||
if ($null -eq $pgroups[$i].Prefer32Bit) {
|
||||
$child = $doc.CreateElement("Prefer32Bit", $doc.DocumentElement.xmlns)
|
||||
$child.InnerText = "false"
|
||||
$pgroups[$i].AppendChild($child) | out-null
|
||||
} else {
|
||||
$pgroups[$i].Prefer32Bit = "false"
|
||||
}
|
||||
|
||||
$pgroupCount++
|
||||
}
|
||||
}
|
||||
|
||||
$doc.Save($file)
|
||||
"-- Set Prefer32Bit=false for $pgroupCount PropertyGroups"
|
Loading…
Add table
Reference in a new issue