Easy Device Integration for Games. EDI Update [12/2023]

What player is a modified version of buttplug?

Thank you for sharing the extra details; they were really helpful.
I managed to implement EDI successfully into my latest game integration. I’ve also included some infos and comments in the code, for those who are interested.

True to its name, using EDI was rather straightforward and the extra features it offers, are great.

I do have a few remarks and possible requests based on what I noticed tho:

  • It appears that when the “Reload Gallery” option is used (more than once), the program stops responding or crashes.

  • It would be great to have some logging functionality. For instance, in FunscriptPlayer, there was at least a “Status” message that showed the name that the injected code was able to pull, or whether it was detecting anything at all. I have now integrated a local logging for the detected gallery names and EDI-Server requests, but it would be nice to have something like this natively integrated, maybe in the Swagger UI.

  • Another small idea is to have a different “definitions.csv” file for each gallery variant. The way I see it, EDI offers three main scripting approaches for the gallery:
    a. Combining all scenes into a single video and making a long script.
    b. Dividing the script into multiple parts (like one for each game stage).
    c. Making several smaller scripts for each animation.
    Since different users might prefer different scripting methods for the same game, having separate .csv files for each variant ensures all versions can be used.
    There are many games that already have most of their animations as separate video files. So I was able to create an automation script that generates the .csv file and allows for each case to be easily generated as well.

  • Probably an issue with the transmission limitation, but if a bluetooth connection is used and the script is too detailed, it will cause big delays on the EDI itself, where even the processing of the http calls (start/stop, ect.) will lag behind and are perform later than they should.

  • The “pause” function stops the device, but actually continues playing the script in the background, essentially skipping parts of it, instead of completely staying idle. I’m not sure if that’s intentional.
    The current issue with this is, if the pause duration lasts longer than the actual script duration for that part, EDI will crash. I’m guessing since the pause only stops the playback and not the script seeking, when it tries to resume, it calls a seek time that doesn’t exist and the program doesn’t like that.
    Regardless, the function itself can be useful in some cases, but would need to be fixed, and having an option to completely halt the script temporarily and then seamlessly resume where it left off, would be great as well (especially for game pausing, etc.).

Thanks again for this awesome tool. I’m looking forward to hopefully seeing more game integrations with it soon :slight_smile:


So after several hours (and days) I have figured out how to make these however my time is extremely limited until later in like two months when all the projects are done.

While looking over some old games and whatnot, figuring out how to get these and call to these animations I was kinda confused about some things. Most games people have done are limited to one set of animations because one outfit so to speak however 2 of them i started to record and splice, they have several outfits and/or Live2D and I have no idea what the animation calls out to… I’ve given up on The Evil Guards of the Merchant City (RJ335038) as I think its too complex for me atm since it has a variety of shapes, sizes, outfit and started on a small RPGM game I’ve been able to figure out thats only had some small sets of animations and think thats going well. Was thinking about renp’y games since i’ve been providing alot of art/skin edits and that seems simple enough. (i think)

Anyhow, is there any advice on handling record/scripting and calling out to multiple outfits? do script one and be done or record one and I suppose call out to the other variants with a copy of the same .funscript?


I would also like to know as I planned on taking a shot at doing “The Dead End ~The Maidens and the Cursed Labyrinth~ ALL IN ONE EDITION (RJ284359)”.

1 Like

Anyway to use this to connect to genesis order? I found the .js file for Lovense integration in the Genesis Order files.


Most of these “outfits” you mentioned can be ignored. You just need to filter out the specifc animation part you want to call a script for.
In the game Milking Farm I filtered for specific strings, based on what the animations are named and am intercepting sprite animations, as well as CG Images. So you can check that plugin as reference.

Live2D animations can be a bit tricky. You can again ignore most variables (shapes, sizes, outfit) that are called alongside them and just focus on the model animation.
I’m still new to this myself, so there might be a better way, but I found that it’s possible to listen to certain events and then detect what Live2D animation is playing based on that.
For the game The Evil Guards of the Merchant City, this could look something like this:

(simple base example)
  // Hook into common event execution
  var _Game_Interpreter_setup = Game_Interpreter.prototype.setup;
  Game_Interpreter.prototype.setup = function(list, eventId) {
     _Game_Interpreter_setup.call(this, list, eventId);
     this._eventId = eventId; // Store the current event ID
     if (this.isOnCurrentMap()) {
        for (var i = 0; i < list.length; i++) {
        var command = list[i];
           if (command.code === 117) { // Call Common Event
              var commonEventId = command.parameters[0];
              var commonEvent = $dataCommonEvents[commonEventId];
              if (commonEvent) {
                 var eventName = commonEvent.name;
                 if (eventName.startsWith("※") || eventName.endsWith("✔")) {
                     logToFile('Event Name:' + eventName);  //replace with your function

  // Hook into sub events
  var old_Game_Interpreter_prototype_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  Game_Interpreter.prototype.pluginCommand = function(command, args) {
     old_Game_Interpreter_prototype_pluginCommand.call(this, command, args);
     var eventId = this._eventId; // Get the current event ID
     var event = $gameMap.event(eventId); // Get the event object
	 //strings to filter for (might need to be adjusted); "CG" to check Live2D Model group
	 var regex = /^(CG|BJ|DS|NP|SA|\$v120|TJ|BS|Again).*$/;
     if (event) {
        if (regex.test(args[1]) || (/^FR$/.test(args))){
          logToFile('Info:' + args);  //replace with your function

Which is then able to detect what Live2D animation is playing, as well as the sub-scene/stage:

Where “SA” and similar variables indicate the stage
(in most cases it looks like 1=init, 2=SpeedNormal, 3=SpeedFast, 4=SpeedFastest, 5=Finish).

The annoying part in this case, is that you need to assign these names to the right animation.
I would suggest going into the unlocked in-game gallery, see what name is detected for each animation and then saving the script with the same name.

This one is actually simpler, since it uses video files in the “www\movies” folder.
Most of them can be filtered out / redirected, since they’re duplicates and would be using the same script.
You can just script these videos directly and then intercept them like this:

(code example)
  player = new Player();

  var MoviePicture_loadVideo = Sprite_Picture.prototype.loadVideo;
  Sprite_Picture.prototype.loadVideo = function () {
      MoviePicture_loadVideo.apply(this, arguments);
      this._scriptName = this._pictureName
          //.replace() Custom string adjustment
      this._scriptRunning = true;
      this._seeked = false;
          function () {
              if (this._scriptRunning) { //add custom name filter here as well
                  logToFile('start: ' + this._scriptName); //replace with your function
                  this._seeked = true;
  const MoviePicture_updateVideo = Sprite_Picture.prototype.updateVideo;
  Sprite_Picture.prototype.updateVideo = function () {
      if (!this.isVideoPicture()) {
          if (this._scriptRunning) {
              this._scriptRunning = false;
              logToFile('stop'); //replace with your function
      MoviePicture_updateVideo.apply(this, arguments);

To detect the video names played:


Thanks for the explanation! I’ll prob pick it back up another time because that game has more content then I think I can manage. As for now im reverting back to something simple/smaller… Some games with a video folder or sprite sheet make it quite easy, so far that I ended up looking back at an old game and viola, I was able to get like one part of it to work, opposed to games that have split pieces, parts, hairs etc because Im still learning on what to label some animations. My whole endgame is to learn L2D ones, they seem more complex but offer more options

Im more of a video/reverse engineering kinda learning person so most of my learning, fun-scripting has been learned from previous samples/work. Once im confident enough and my SR6 finally gets to me I can take bigger/more fun projects and share (self conscious of my own work heh)

Again thanks for the explanation!


I’ve had this issue with a few games using EDI. Appreciate the work that’s gone into this, but when I load a game with this integration, it works but after about like 12 seconds it’ll just crash, or quit out with no error, and I have to restart it, making it kinda completely broken for me unfortunately.

Any idea what could be causing this? Using .net 6 “dotnet-sdk-6.0.415-win-x64” on Windows 10. On a fresh install too, was having this issue before on my old machine as well.

Can I ask a stupid question!

On the settings for this I assume to setup I simply add my handy key into that box and leave the iniface url as ws://localhost:12345 ?

I cant seem to connect / see no devices?

If I start a game with it open - i can see its getting commands passed to EDI but my handy does nothing :frowning:

Thank you and apologies for the noob question

@easycarry did you get this resolved? I’m having the same issue, but mine just closes after a minute of use.

I logged on this morning thought would have another go and mine has started doing the same just closing after a minute.

looking in log seems like .net isnt happy saying request address is not valid. Just starting to troubleshoot so will update if I find anything.

tried re-downloading with the demo files - it opened and didnt auto close.
i added handy key - nothing happened tried reconnect nothing. If i go into swagger via the link it would pass a command.

closed the app - just re-launched and back to it auto closing again after a minute

Okay found my issue! NordVPN must be killing it or conflicting!

I just disconnected my vpn and straight away

@easycarry \ @cloudviiv I dont know if either of you have vpn or anything filtering your traffic in anyway but the crashes and it detecting my handy all okay when disconnected vpn connection.

adding a rule in to work around that.


So after testing I found that my problem occurs when I attach multiple devices to edi (specifically my SR6 & diamo). It’s as if it’s crashed due to too much info or something. Whenever I use my SR6 alone though it works just fine.

1 Like

hello i am working on this.


hey thanks for the feedback, try this new version let me know if it worked well for you


Thanks for your continuous development!

Could you provide some more details regarding the latest features?
I’m especially interested in the points “Handy bluetooth Precisión”, “Command Delay in Config,” and “Add ready colum in devices grid”.
How do these options work exactly?

Also, I noticed that the .zip variant without the videos still has the older EDI version. Maybe you can update that one as well.

1 Like

i Basically refactor the command sending logic so that they do not overlap. and are always managed from individual threads.
in the edi config
“buttplug”: {
“Url”: “ws://localhost:12345”,
“CommandDelay”: 50
The vibrators and osilators send commands every 50ml while the handy sends one command at a time and if the command lasts less than 50mliseconds it skips it. It also adjusts command timings so that they are always sent with the correct milliseconds based on the current millisecond so you never get out of sync.

yes, I have to update those links and update the tutorial too.
since now you can get the definitions from the funscript chapters

1 Like

Hey, just to mention - downloaded the new version - initial tests seem fine. It caused Avast to go nuts in wanting to block it have added an exception and still have the one in nordvpn. Seems to work fine in an initial try out :wink:

1 Like

@dimnogro after testing it seems to work just fine

1 Like