Trying to get ffi
and require()
to work somehow
require
might not actually be scuffed if this error can be resolved?
loadfile
throws the same error
Latest error Loading with dofile
06:04:58 PM FATAL ERROR: (C:\projects\payday2-superblt\src\InitiateState.cpp:320) mods/HeistersHaptics/mod.lua:1: attempt to call global 'loadfile' (a nil value)
stack traceback:
mods/HeistersHaptics/mod.lua:1: in main chunk
[C]: in function 'dofile'
mods/base/base.lua:169: in function 'RunHookFile'
mods/base/base.lua:157: in function 'RunHookTable'
mods/base/base.lua:189: in function 'OrigRequire'
mods/BeardLib/Core.lua:406: in function 'require'
[string "lib/setups/setup.lua"]:133: in main chunk
[C]: in function 'require'
mods/base/base.lua:188: in function 'OrigRequire'
mods/BeardLib/Core.lua:406: in function 'require'
[string "lib/setups/menusetup.lua"]:1: in main chunk
[C]: in function 'require'
mods/base/base.lua:188: in function 'OrigRequire'
mods/BeardLib/Core.lua:406: in function 'require'
[string "lib/entry.lua"]:13: in main chunk
[C]: in function 'require'
mods/base/base.lua:188: in function 'OrigRequire'
mods/BeardLib/Core.lua:406: in function 'require'
[string "core/lib/coreentry.lua"]:19: in main chunk
Issue is probably from the require
for ffi
in pollnet
Ask Siri if she knows if I can get a copy of ffi.lua
somewhere?
BeardLib
seems to be causing cancer here but I haven’t tried turning it off
Technically if BeardLib
stops trying to resolve the require OrigRequire
from SuperBLT
should run the mod just fine
This happens because SuperBLT
is checking for available Hooks on the pass path which seems to trigger some BeardLib
registered Hook
Full documented Stack trace
lib/utils/dev/api/TestAPI âť—âť—âť—Not checked yet
If the line count is continually counted throughout files the culprit may be either one of the following statements
It seems that loading things via dofile()
DOES work but returning an object doesn’t
It’s possible to define one globally
Sadly this doesn’t solve the ffi
issue
Get an external ffi
library and use it via dofile()
, etc.
Options:
Building and using these in linux, outside of the Payday 2 environment, worked without issues.
Building on windows I’ve only succeeded in doing for cffi-lua
, however loading the .dll
fails with a number of different errors depending on the load method used for the attempt.
Types of errors seen with different attempts to load a library (basically same for all of them)
dofile(ModPath .. "cffi.dll")
09:11:48 AM FATAL ERROR: (C:\projects\payday2-superblt\src\InitiateState.cpp:300) mods/HeistersHaptics/cffi.dll:1: '=' expected
package.loadlib(ModPath .. "cffi.dll", "luaopen_cffi")
08:36:16 AM FATAL ERROR: (C:\projects\payday2-superblt\src\InitiateState.cpp:320) mods/HeistersHaptics/mod.lua:6: %1 is not a valid Win32 application.
BeardLib:OrigRequire(ModPath .. "cffi")
09:35:07 AM FATAL ERROR: (C:\projects\payday2-superblt\src\InitiateState.cpp:320) mods/base/base.lua:184: attempt to call method 'lower' (a nil value)
stack traceback:
mods/base/base.lua:184: in function 'OrigRequire'
mods/HeistersHaptics/mod.lua:8: in main chunk
[C]: in function 'dofile'
mods/base/base.lua:169: in function 'RunHookFile'
mods/base/base.lua:157: in function 'RunHookTable'
mods/base/base.lua:189: in function 'OrigRequire'
mods/BeardLib/Core.lua:406: in function 'require'
[string "lib/setups/setup.lua"]:133: in main chunk
[C]: in function 'require'
mods/base/base.lua:188: in function 'OrigRequire'
mods/BeardLib/Core.lua:406: in function 'require'
[string "lib/setups/menusetup.lua"]:1: in main chunk
[C]: in function 'require'
mods/base/base.lua:188: in function 'OrigRequire'
mods/BeardLib/Core.lua:406: in function 'require'
[string "lib/entry.lua"]:13: in main chunk
[C]: in function 'require'
mods/base/base.lua:188: in function 'OrigRequire'
mods/BeardLib/Core.lua:406: in function 'require'
[string "core/lib/coreentry.lua"]:19: in main chunk
This error tells me that it still runs through BLT’s overridden require function, as it fails on called path:lower()
in the override function.
This occurs because path
in this case is actually a function.
Calling this function fails in BeardLib
’s ModCore:GetSettings()
require("cffi")
or BLT:require("cffi")
Both crash the game on launch without an error log present. Might try later with a later running hook.
I give up
Loading .so
and .o
files works without issue via dofile()
or package.loadlib()
Having a compiled ffi
library from one of the various other sources doesn’t come with Win32 support which disqualifies it for the Payday2 modding purpose.
This is wrong apparently as other libs claim Win x86 support.
Building cffi-lua
on windows with a windows built LuaJIT 2.1.0-beta2
has actually produced results, albeit not functional ones.
Turns out Payday 2 does NOT use LuaJIT 2.1.0-beta2
unlike claimed online.
The actual version used is LuaJIT 2.1.0-beta3
I have cancer it’s 2:30am and I’m going to go to sleep for now and retry all of this with
LuaJIT 2.1.0-beta3
tomorrow.
Rebuilding with LuaJIT 2.1.0-beta3
did not work either. It still isn’t detected as a proper Win32 application
It doesn’t seem possible in a SuperBLT
context to do this.
I will switch to Siri’s proposed approach in the readme of Heisters-Hapticsand leave this alone for now.
NOTE: This code currently not work. This is mostly because BLT has issues calling require (believe me I've tried), and can't use the FFI library.
Here's my solution (that is 100% guaranteed to work im sure):
BLT does have (at least functioning enough to open `calc.exe`) support for `io.popen()`. This means - we can open a second program with dynamic information.
Here's the plan:
`haptic_serve.exe`: A program that runs as a daemon that is launched during `HapticsCore:init()`, and only gets closed by the user. This actually holds the connection with the Buttplug Websocket.
`haptic_ctl.exe`: A program that is run whenever vibration strength updates need to be issued, with arguments that inform it of how to alter the vibrations.
This then communicates (through a named pipe or some other IPC method we'll figure it out later) with `haptic_serve.exe`
Alternatively, this could be done with a python script or something for system compatability - I'll need to think on this for a bit.
Separate daemon and client
Software stack
Lua will obviously have to be used for the mod side of things, exact integration with SuperBLT
or BeardLib
I will look at later.
The Daemon will be written in rust. It will handle WebSocket communication with the Buttplug server either via the Buttplug crate which uses tungstenite-rs or alternatively I’ll write my own solution using fasterwebsockets which is more performant for the type of data we’ll send here.
Communication
Lua’s IPC capabilities are kind of horrendous without external library support.
While on UNIX systems I could probably run os.execute('mkfifo')
or something of the sort, this would do nothing at all on Windows systems. Windows does have pipes but the way they work is very different from UNIX pipes and would require a lot of weird code if communication is at all possible from this specific Lua context.
Considering io
is available in a SuperBLT
context, the only reasonable solution excluding memory mapped files is to read/write to a text file in the mod directory.
Possible approach
It would be possible to read the last modified datetime of the text file in rust. Run this every second since updates won’t appear often and only if the last modified date is different than the last modified datetime in cache, will rust read out the file and communicate the content to the WebSocket.
flowchart TD
  B(IPC file)
  subgraph mod
  A[Lua]
  end
  A -->|Write state change to IPC file| B
  subgraph daemon
  C[Rust]
  C --> D[[Check IPC file modified datetime every second]]
  E[Retrieved last modified datetime]
  E -->F{Is after last\nrecorded\nmodified\ndatetime?}
  F -->|yes| G(Communicate to WebSocket)
  G --> D
  end
  D -->|Check for change| B
  B -->|Last modified datetime| E
  F -->|no| D
Seems to be the most reasonable approach for now and I’ll keep working with this for the moment.
Running exe files from the mod side
Obviously I’ll have to run the daemon from the mod and not have users start it by hand whenever they start Payday 2, since that would be kind of horrible.
I managed with some difficulty to run an exe from the lua
mod without blocking the Payday 2 process until the end of execution. However I need some kind of Hook to close the process again since Payday 2 won’t fully exit without closing it. Running coroutine.stop()
might work if I can hook it into a sort of exit hook
?
I want to pass the file path for the IPC file as a command line argument to the daemon.
This works although I had some issues with start, since start apparently takes the first argument starting with quotes to set the window title.
This is a bit ugly but works for purpose. It does open a console window but that’s something I’ll try to circumvent later. Adding /C
to start does NOT work.
Time to get to the real meat of the task.
Let the daemon detect file changes and react to them
So the idea of having to check every second is nice and all but what if I could just asynchronously watch for file changes and react to them… well notify exists so that seems like a reasonable approach to try before anything else.
Quick and dirty implementation to read out the first argument passed to the executable and use it as a path to check for changes for (actual async_watch
implementation is in a different file).
Tried it with the log file that SuperBLT
creates and it seems to work just fine.
changed: Event { kind: Modify(Any), paths: ["C:\\Program Files (x86)\\Steam\\steamapps\\common\\PAYDAY 2\\mods\\logs\\2024_05_01_log.txt"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None }
Where the path to log was passed as an argument, just like I did in the Lua example in the previous section.
IPC-File content format
I’m not quite sure what format the actual messages in the IPC file should have. It’s possible for them to just be a simple +x
-x
format for the vibration intensity, however I’m not sure if that’ll suffice. For now I’ve set up JSON parsing from the IPC file, along with ignoring any modes are than modified
because I don’t want to accidentally capture the creation (or manual use deletion) of it.
Failing to read the file or the JSON values out of it is just ignored with an error log at the moment but it seems to be the most reasonable way to handle those issues.
"Could not read content of IPC file. Ignoring last command..."
I’ll discuss the actual format of the message with Siri, she has some experience with this after all.
DLL angle?
While talking to Siri I remembered that Lua can load in .dll
files written in C that have the appropriate Lua bindings and export a luaopen_packagename
.
Surely those bindings should exist for rust too right? They do.
This is actually great news since it could let me circumvent the entire “running an exe as a separate process and communicating with it via IPC file” thing. I’ll have to look into that a bit later.
Although it seems that building a Win32 .dll
file from Linux is connected to a bit of cancer I’ll have to read into more.
However this does look quite promising if I can get it to compile properly and load it with dofile()
.
This was abandoned quickly for the same issues as loading any other dll
didn’t work before.
Let’s ask the experts
I caved and went to the modworkshop discord server, to ask someone if there’s any way to load a dll
into SuperBLT without it telling me to stop performing blocking go die.dns
calls from the main thread
Well known Payday 2 modder, contributor to SuperBLT, and creator of HopLib: Hoppip
responded to me pretty quickly and pointed out, that SuperBLT has a Native Plugin Template and Native Plugin Library, which are specifically designed to create dll
s to be loaded in via blt.load_native()
and their definition in a supermod.xml
file.
Now this was huge news and I took quite a bit of time to explore this method in detail, over at SuperBLT Native Plugin Template. I recommend you give it a read if you want to see me slowly descend into insanity.
How do I make this async?
Now that I can create and load a DLL into my lua
file, how do I actually create an asynchronous listener to facilitate my Buttplug WebSocket connection?