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(); } }