Ever been frustrated that a software package was missing a feature you want? In the best-case scenario, the software would be open source and you could just tweak the code and rebuild. But in many cases, the software is closed-source. In the case of [Faster than lime], he found a SNES emulator (Snes9X) that didn’t support controllers to showcase the technique. So with a little bit of Rust, he wrote some code that could be injected into the emulator via DLL injection.
It’s a fantastic tutorial that shows the technique. He starts by creating a Rust project that uses the DLL-Syringe crate (the rust version of dependency management). This crate does much of the heavy lifting involved with injecting a DLL into a target process. The rest of the journey is an excellent process of going through the Windows documentation and implementing the features. The DLL just reads the controller and then sends the right input to the program. In the end, [Faster than lime] has a great injected DLL and we have a wonderful time learning about Rust and debugging in an injection environment!
It’s been a while since we last covered DLL injection, and it’s nice to see how the process has evolved. Video after the break.
Wait, Snes9x *is* open source isn’t it?
Indeed, it is https://github.com/snes9xgit/snes9x
That’s an awful lot of work to avoid merely adding a feature to an open source program.
So, a typical tRust evangelist, then.
“crate (the rust version of dependency management)”
That says it all. Make any project have a multi-gigabyte build environment by downloading and building the entire dependency tree at once, Gentoo-style, while ignoring all existing package managers and build systems…
For faster iterations and application development, this works easier than the alternative unless all your deps already have a Debian package and you only have to build on Linux (and only one distro). That said, it’s not strictly an either-or: numerous Rust crates have corresponding official Debian packages, though with the inherent stabilization delay involved. If crates really bother you, you can do things other ways if you like. But my impression is that many view having a consistent dependency management story and system as a positive, not a negative.
A crate is simply the Rust equivalent of a library. It can be distributed as a binary. Rust has strong dependency management inline with its ethos of making bugs difficult to write, so you must declare the version of a crate that you are using.
Yes, that can be constraining, but it has advantages too. In an age where disk space and memory is plentiful statically-linked binaries aren’t necessarily the issue they once were. Software complexity is a major challenge for QA teams to manage, and dynamic linking plays a part in that.
Consider a binary that relies on just 5 third-party libraries. Lets say that each library has only ever released two versions. With static linking you define which of those versions are being used at build time, and that’s what your QA team are testing. With dynamic linking you might make assumptions about which versions your binary is using, but there are 2^5=32 possible permutations that might be in use in customer environments, and that is effectively impossible to test.
For real-world applications with many libraries of many possible versions, the permutations soon become literally impossible to test. So the common assumption is that the latest version of a library contains fewer bugs than any that came before, and that is usually the one tested. Certainly with modern CI build and test systems, libraries have much improved in that area since the DLL hell days of the mid ’90s. However there are no guarantees that earlier versions aren’t in use, or that a later version doesn’t come along and break your application. The only guaranteed solution is to provide the exact version of each library that you tested against and to force your application to use it. At that point, static linking isn’t that much different…
“Make bugs hard to write” sounds great and all, but I always assume everything I write, and all my dependencies, are broken in some subtle way that will only reveal itself in time. It’s far from perfect, but the only package management approach I know of that fits this model of the world is semver. Yes, a patch release could introduce more bugs than it fixes, but it’s impossible to prove one way or the other. Either you trust that your whole ecosystem is broadly improving over time, or you take your chances with the bugs that your fixed-version deps have today. Just don’t pretend that one approach is free from risk.
While you’re technically correct (the best kind of correct), this work is useful to show people (I for one) learn about DLL injection with a practical example.
It would be good if the video taught about DLL injection using a practical language, too.
What would be a practical language?
I’m sorry your life is so full of sadness that you feel the need to hate on other people’s favorite things.
Might have been for training on the technique, so you can tell what’s going on both ends, as opposed to something opaque that you want to do it to.
Indeed it is much easier to troubleshoot injected code if you can see the code it’s injected into.
Awesome video, thanks for sharing.
Thanks for the tips, learned a lot of great tricks from this