diff --git a/UnityIGCSConnector/UnityIGCSConnector.sln b/UnityIGCSConnector/UnityIGCSConnector.sln
new file mode 100644
index 0000000..d2dd885
--- /dev/null
+++ b/UnityIGCSConnector/UnityIGCSConnector.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34723.18
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnityIGCSConnector", "UnityIGCSConnector.vcxproj", "{676E7EC4-E40E-4D59-9587-2E2CD04BC562}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Debug|x64.ActiveCfg = Debug|x64
+ {676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Debug|x64.Build.0 = Debug|x64
+ {676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Debug|x86.ActiveCfg = Debug|Win32
+ {676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Debug|x86.Build.0 = Debug|Win32
+ {676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Release|x64.ActiveCfg = Release|x64
+ {676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Release|x64.Build.0 = Release|x64
+ {676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Release|x86.ActiveCfg = Release|Win32
+ {676E7EC4-E40E-4D59-9587-2E2CD04BC562}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {CCC0598B-A93B-4F40-B29D-43164253230A}
+ EndGlobalSection
+EndGlobal
diff --git a/UnityIGCSConnector/UnityIGCSConnector.vcxproj b/UnityIGCSConnector/UnityIGCSConnector.vcxproj
new file mode 100644
index 0000000..8a9dd2b
--- /dev/null
+++ b/UnityIGCSConnector/UnityIGCSConnector.vcxproj
@@ -0,0 +1,167 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 17.0
+ Win32Proj
+ {676e7ec4-e40e-4d59-9587-2e2cd04bc562}
+ UnityIGCSConnector
+ 10.0
+
+
+
+ DynamicLibrary
+ true
+ v143
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+ DynamicLibrary
+ true
+ v143
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UnityIGCSConnector
+ $(SolutionDir)..\$(Configuration)\
+ false
+
+
+ UnityIGCSConnector
+
+
+ UnityIGCSConnector
+
+
+ UnityIGCSConnector
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+
+
+ Windows
+ true
+ false
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+
+
+ Windows
+ true
+ true
+ true
+ false
+
+
+
+
+ Level3
+ true
+ _DEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ NotUsing
+ pch.h
+
+
+ Windows
+ true
+ false
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;UNITYIGCSCONNECTOR_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ NotUsing
+ pch.h
+ stdc11
+
+
+ Windows
+ true
+ true
+ true
+ false
+
+
+ xcopy /y /d "$(SolutionDir)$(Platform)\$(Configuration)\UnityIGCSConnector.dll" "$(SolutionDir)..\$(Configuration)\UnityIGCSConnector.dll"
+
+
+
+
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/UnityIGCSConnector/UnityIGCSConnector.vcxproj.filters b/UnityIGCSConnector/UnityIGCSConnector.vcxproj.filters
new file mode 100644
index 0000000..29b7304
--- /dev/null
+++ b/UnityIGCSConnector/UnityIGCSConnector.vcxproj.filters
@@ -0,0 +1,22 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/UnityIGCSConnector/dllmain.c b/UnityIGCSConnector/dllmain.c
new file mode 100644
index 0000000..e16ef01
--- /dev/null
+++ b/UnityIGCSConnector/dllmain.c
@@ -0,0 +1,93 @@
+// dllmain.cpp : Defines the entry point for the DLL application.
+#include
+#include
+#include
+
+#define EXPOSE __declspec(dllexport)
+
+// The function that will be the one called from the C# side, that will update the corresponding values.
+typedef void (*MoveCameraCallback)(float, float, float, int);
+
+typedef void (*SessionCallback)(void);
+
+typedef uint8_t* (*GetCameraDataFunc)(void);
+
+MoveCameraCallback GlobalCallback = NULL;
+SessionCallback GlobalStartSession = NULL;
+SessionCallback GlobalEndSession = NULL;
+
+// There are things that only needs to be run once.
+static int first_initialization = 1;
+
+EXPOSE int IGCS_StartScreenshotSession(uint8_t _ignore) {
+ if (GlobalStartSession) {
+ GlobalStartSession();
+ printf("Called StartSession\n");
+ }
+ return 0;
+}
+
+EXPOSE void IGCS_EndScreenshotSession() {
+ GlobalEndSession();
+ printf("Called EndSession\n");
+}
+
+EXPOSE uint8_t* U_IGCS_Initialize(MoveCameraCallback cb, SessionCallback start_cb, SessionCallback end_cb) {
+ AllocConsole();
+ printf("Initializing callback\n");
+ GlobalCallback = cb;
+ GlobalStartSession = start_cb;
+ GlobalEndSession = end_cb;
+
+ // Load IGCS
+ HMODULE igcs = LoadLibraryA("IgcsConnector.addon64");
+
+ if (!igcs) {
+ MessageBoxA(
+ NULL,
+ "IgcsConnector.addon64 was not found, make sure it is in the same directory as the executable.",
+ "Unable to find IgcsConnector",
+ MB_OK | MB_ICONERROR);
+ return NULL;
+ }
+
+ FARPROC cameraToolsFunction = GetProcAddress(igcs, "connectFromCameraTools");
+ GetCameraDataFunc getCameraData = (GetCameraDataFunc)GetProcAddress(igcs, "getDataFromCameraToolsBuffer");
+
+ if (first_initialization) {
+ cameraToolsFunction();
+ first_initialization = 0;
+ }
+
+ // TODO: move this where it belongs. Maybe at some point we should actually fill in the data.
+ uint8_t* cameraData = getCameraData();
+ cameraData[0] = 0;
+
+ printf("Camera connected!\n");
+
+ return cameraData;
+
+}
+
+EXPOSE void IGCS_MoveCameraPanorama() {}
+
+EXPOSE void IGCS_MoveCameraMultishot(float step_left, float step_up, float fov, int from_start) {
+ GlobalCallback(step_left, step_up, fov, from_start);
+ return;
+}
+
+BOOL WINAPI DllMain(HMODULE hModule,
+ DWORD ul_reason_for_call,
+ LPVOID lpReserved
+)
+{
+ switch (ul_reason_for_call)
+ {
+ case DLL_PROCESS_ATTACH:
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+ return TRUE;
+}
\ No newline at end of file
diff --git a/build.ps1 b/build.ps1
index 9cb9841..625baf8 100644
--- a/build.ps1
+++ b/build.ps1
@@ -2,6 +2,8 @@ cd UniverseLib
.\build.ps1
cd ..
+# ----------- Build UnityIGCSConnector ------------
+msbuild.exe UnityIGCSConnector/UnityIGCSConnector.sln -p:Configuration=Release
# ----------- MelonLoader IL2CPP (net6) -----------
dotnet build src/CinematicUnityExplorer.sln -c Release_ML_Cpp_net6preview
diff --git a/src/Cinematic/UnityIGCSConnector.cs b/src/Cinematic/UnityIGCSConnector.cs
new file mode 100644
index 0000000..4879fdc
--- /dev/null
+++ b/src/Cinematic/UnityIGCSConnector.cs
@@ -0,0 +1,129 @@
+using MonoMod.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using UnityExplorer;
+
+namespace CinematicUnityExplorer.Cinematic
+{
+ // StepCommand is basically the offset of step_left and step_up, what IGCS sends to move the camera.
+ using StepCommand = Mono.CSharp.Tuple;
+
+ internal static class NativeMethods
+ {
+ [DllImport("kernel32.dll")]
+ public static extern IntPtr LoadLibrary(string dll);
+
+ [DllImport("kernel32.dll")]
+ public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
+ }
+ public class UnityIGCSConnector
+ {
+ // UnityIGCSConnector.dll definitions.
+ private delegate void MoveCameraCallback(float step_left, float step_up, float fov, int from_start);
+ private delegate void SessionCallback();
+ private delegate IntPtr U_IGCS_Initialize(MoveCameraCallback callback, SessionCallback start_cb, SessionCallback end_cb);
+
+ // Store the initial position when a session start in IGCSDof.
+ Mono.CSharp.Tuple position = null;
+
+ private readonly bool isValid = false;
+ private bool _isActive = false;
+ public bool IsActive => isValid && _isActive;
+
+ // Delegate holder for the MoveCamera, StartSession and EndSession.
+ private readonly List delegates = new();
+
+ // Since some games use multi-threaded, in order to make sure we're only moving things during
+ // the main thread is executing, we use this Queue to enqueue the move commands and dequeue them in the Update function.
+ // This object *must* be used with a Lock.
+ private readonly Queue commands = new();
+
+ private IntPtr CameraStatus = IntPtr.Zero;
+ // In order to avoid allocations on every Update call, we create this buffer to allocate once
+ // and copy from here the CameraStatus (because Marshal.Copy requires a buffer, urgh).
+ private readonly byte[] CameraStatusBuffer = new byte[] { 0x0 };
+
+ public void UpdateFreecamStatus(bool enabled)
+ {
+ if (CameraStatus == IntPtr.Zero) return;
+
+ CameraStatusBuffer[0] = enabled ? (byte)0x1 : (byte)0x0;
+ Marshal.Copy(CameraStatusBuffer, 0, CameraStatus, 1);
+ }
+
+ public void ExecuteCameraCommand(Camera cam)
+ {
+ var transform = cam.transform;
+ if (!_isActive || position == null)
+ {
+ position = new(transform.position, transform.rotation);
+ }
+
+ if (!_isActive || position == null) { return; }
+
+ StepCommand c = null;
+
+ lock (commands)
+ {
+ if (commands.Count <= 0) return;
+ c = commands.Dequeue();
+ }
+
+ transform.position = position.Item1;
+ transform.rotation = position.Item2;
+ transform.Translate(c.Item1, c.Item2, 0.0f);
+ }
+
+ private void MoveCamera(float stepLeft, float stepUp, float fov, int fromStart)
+ {
+ lock (commands)
+ {
+ commands.Enqueue(new StepCommand(stepLeft, stepUp));
+ }
+ }
+ private void StartSession()
+ {
+ _isActive = true;
+ }
+
+ private void EndSession()
+ {
+ position = null;
+ _isActive = false;
+
+ lock (commands)
+ commands.Clear();
+ }
+
+ public UnityIGCSConnector()
+ {
+ var lib = NativeMethods.LoadLibrary(@"UnityIGCSConnector.dll");
+ if (lib == IntPtr.Zero)
+ {
+ ExplorerCore.LogWarning("UnityIGCSConnector.dll was not found so IGCSDof will not be available");
+ return;
+ }
+
+ var func = NativeMethods.GetProcAddress(lib, @"U_IGCS_Initialize");
+ if (func == IntPtr.Zero)
+ {
+ throw new EntryPointNotFoundException("Failed to find 'U_IGCS_Initialize' which means you can have a corrupt UnityIGCSConnector.dll.");
+ }
+
+ var initFunc = (U_IGCS_Initialize)Marshal.GetDelegateForFunctionPointer(func, typeof(U_IGCS_Initialize));
+
+ delegates.Add(new MoveCameraCallback(MoveCamera));
+ delegates.Add(new SessionCallback(StartSession));
+ delegates.Add(new SessionCallback(EndSession));
+
+ CameraStatus = initFunc((MoveCameraCallback)delegates[0], (SessionCallback)delegates[1], (SessionCallback)delegates[2]);
+ if (CameraStatus == IntPtr.Zero)
+ throw new InvalidDataException("IGCSDof returned an invalid pointer which means something went wrong");
+
+ isValid = true;
+ }
+ }
+}
diff --git a/src/UI/Panels/FreeCamPanel.cs b/src/UI/Panels/FreeCamPanel.cs
index e44adae..690f699 100644
--- a/src/UI/Panels/FreeCamPanel.cs
+++ b/src/UI/Panels/FreeCamPanel.cs
@@ -4,6 +4,9 @@ using UnityExplorer.Inspectors;
using UniverseLib.Input;
using UniverseLib.UI;
using UniverseLib.UI.Models;
+using System.Runtime.InteropServices;
+using CinematicUnityExplorer.Cinematic;
+
#if UNHOLLOWER
using UnhollowerRuntimeLib;
#endif
@@ -76,9 +79,12 @@ namespace UnityExplorer.UI.Panels
private static FreecamCursorUnlocker freecamCursorUnlocker = null;
+ public static UnityIGCSConnector connector = new();
+
internal static void BeginFreecam()
{
inFreeCamMode = true;
+ connector.UpdateFreecamStatus(true);
previousMousePosition = IInputManager.MousePosition;
@@ -177,6 +183,7 @@ namespace UnityExplorer.UI.Panels
internal static void EndFreecam()
{
inFreeCamMode = false;
+ connector.UpdateFreecamStatus(false);
if (usingGameCamera)
{
@@ -254,6 +261,9 @@ namespace UnityExplorer.UI.Panels
if (positionInput.Component.isFocused)
return;
+ if (connector.IsActive)
+ return;
+
lastSetCameraPosition = ourCamera.transform.position;
positionInput.Text = ParseUtility.ToStringForInput(lastSetCameraPosition);
}
@@ -672,7 +682,7 @@ namespace UnityExplorer.UI.Panels
Transform transform = FreeCamPanel.ourCamera.transform;
- if (!FreeCamPanel.blockFreecamMovementToggle.isOn && !FreeCamPanel.cameraPathMover.playingPath){
+ if (!FreeCamPanel.blockFreecamMovementToggle.isOn && !FreeCamPanel.cameraPathMover.playingPath && !FreeCamPanel.connector.IsActive) {
ProcessInput();
}
@@ -692,6 +702,8 @@ namespace UnityExplorer.UI.Panels
FreeCamPanel.followObjectLastRotation = FreeCamPanel.followObject.transform.rotation;
}
+ FreeCamPanel.connector.ExecuteCameraCommand(FreeCamPanel.ourCamera);
+
FreeCamPanel.UpdatePositionInput();
}
}