Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feature Request 'Unity as Library' Change Data Folder

Discussion in 'Editor & General Support' started by CyRaid, Feb 5, 2021.

  1. CyRaid

    CyRaid

    Joined:
    Mar 31, 2015
    Posts:
    134
    I'm using the Unity as Library UnityLibrary.dll, and calling the UnityMain, but I really do not want the data folder next to my executable.

    Can you *please* add, say, a command line option to change the data directory to a different path? Even as much as allowing me to set current directory first if you really need to.

    I'm sure there would be many pleased in general if you allowed a player command line to change the data directory as well.
     
  2. Teolog

    Teolog

    Joined:
    Jun 4, 2014
    Posts:
    4
    +1.
    Try use unity as library but can't. Unity data search path hardcoded to main process executable, but not UnityPlayer.dll module and can't be changed.
     
  3. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,645
    Pass in "-datafolder" command line argument when invoking UnityMain, like "-datafolder path\to\my\data\folder".
     
  4. Teolog

    Teolog

    Joined:
    Jun 4, 2014
    Posts:
    4
    "-datafolder absolutepath" not work at least for Unity 2021.3.24.
    For some reason Unity still not use defined folder, but searching for "{exename}_data" in directory there exe located.
    Error:

    Application folder:
    D:/ProjectDir/bin/Debug/net6.0
    There should be 'SimpleEngine_Data'
    folder next to the executable
     
  5. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,645
    Ah darn, that was added to Unity 2022.2.
     
  6. TheKramer

    TheKramer

    Joined:
    Aug 23, 2021
    Posts:
    1
    > Ah darn, that was added to Unity 2022.2.

    Tried this with Unity 2023.3. It did not seem to work. Is this feature actually implemented?

    I couldn't find it in the command-line documentation: https://docs.unity3d.com/2023.3/Documentation/Manual/PlayerCommandLineArguments.html

    EDIT: This seems to work, however without the mono runtime (MonoBleedingEdge) being in the same folder as the executable, this seems to still fail.

    This folder structure Unity requires is quite rigid. Is there no hope for developers who would like to execute UnityMain from a path that's completely outside of Unity's required folder structure?
     
    Last edited: Oct 2, 2023
  7. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,645
    That should work with IL2CPP, I believe. MonoBleedingEdge folder is hardcoded to be next to the executable, though :(.
     
  8. TheZombieKiller

    TheZombieKiller

    Joined:
    Feb 8, 2013
    Posts:
    265
    Unity seems to use GetModuleFileNameA/GetModuleFileNameW to determine the "base directory" that it'll search in for MonoBleedingEdge, D3D12, the crash handler, etc. You can modify the import address table (IAT) of UnityPlayer.dll before you call UnityMain to redirect those calls to your own function that provides a different path.

    Assuming your executable is written in C++17, here's some sample code that will change an entry in the IAT:
    Code (csharp):
    1. static PIMAGE_IMPORT_DESCRIPTOR GetImportDirectory(HMODULE module)
    2. {
    3.     auto dosHeader = (PIMAGE_DOS_HEADER)module;
    4.     auto ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)module + dosHeader->e_lfanew);
    5.     auto directory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    6.     return (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)module + directory.VirtualAddress);
    7. }
    8.  
    9. static void SetModuleImport(HMODULE module, LPCSTR importModuleName, LPCSTR importName, DWORD_PTR function)
    10. {
    11.     auto imports = GetImportDirectory(module);
    12.     auto importModule = GetModuleHandleA(importModuleName);
    13.  
    14.     for (int i = 0; imports[i].Name != 0; i++)
    15.     {
    16.         if (auto handle = GetModuleHandleA((LPCSTR)((DWORD_PTR)module + imports[i].Name)); handle != importModule)
    17.             continue;
    18.  
    19.         auto thunks = (PIMAGE_THUNK_DATA)((DWORD_PTR)module + imports[i].FirstThunk);
    20.         auto originalThunks = (PIMAGE_THUNK_DATA)((DWORD_PTR)module + imports[i].OriginalFirstThunk);
    21.  
    22.         for (int j = 0; originalThunks[j].u1.AddressOfData != 0; j++)
    23.         {
    24.             auto import = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)module + originalThunks[j].u1.AddressOfData);
    25.  
    26.             if (strcmp(import->Name, importName) == 0)
    27.             {
    28.                 DWORD oldProtect;
    29.                 VirtualProtect(&thunks[j].u1.Function, sizeof DWORD_PTR, PAGE_READWRITE, &oldProtect);
    30.                 thunks[j].u1.Function = function;
    31.                 VirtualProtect(&thunks[j].u1.Function, sizeof DWORD_PTR, oldProtect, &oldProtect);
    32.                 return;
    33.             }
    34.         }
    35.     }
    36. }
    37.  
    If you want the engine to search for MonoBleedingEdge etc next to UnityPlayer.dll, you can use these functions:
    Code (csharp):
    1. static DWORD WINAPI DGetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize)
    2. {
    3.     if (hModule == GetModuleHandleA(nullptr))
    4.         hModule = GetModuleHandleA("UnityPlayer.dll");
    5.  
    6.     return GetModuleFileNameA(hModule, lpFilename, nSize);
    7. }
    8.  
    9. static DWORD WINAPI DGetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize)
    10. {
    11.     if (hModule == GetModuleHandleA(nullptr))
    12.         hModule = GetModuleHandleA("UnityPlayer.dll");
    13.  
    14.     return GetModuleFileNameW(hModule, lpFilename, nSize);
    15. }
    And enable them using:
    Code (csharp):
    1. auto module = LoadLibraryW(L"path/to/UnityPlayer.dll");
    2. SetModuleImport(module, "KERNEL32", "GetModuleFileNameA", (DWORD_PTR)&DGetModuleFileNameA);
    3. SetModuleImport(module, "KERNEL32", "GetModuleFileNameW", (DWORD_PTR)&DGetModuleFileNameW);
     
  9. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,645
    Nice workaround!