Other than that, you’d be right :-)
OP didn't do this, because as they said: "I don’t like writing assembly". Although what they did is arguably harder...which maybe was the point! After all they could've just hooked up an MCU to proxy the button press for a given duration, which would've taken a lot less time.
The usual approach for a non-trivial patch is exactly what the OP did here: put your patch somewhere in free space, and replace the original sequence of instructions with a jump to your patch.
DnSpy lets you do this (that's why I was careful to prefix decompilers with "native"in my answer). You can edit snippets of decompiled C# code directly inside. But vm based runtimes are much easier to decompile/recompile, and it still may not work in the end.
Though programs compiled to intermediate code are in general very different to analyze, so that's kind of nitpicking.
With C and C++ things are a lot harder.
This is why traditional binary patching is the usual go-to option, because it doesn't require much upfront effort to perform. The downside is that you're constrained by the memory map of the original program and contorting your modifications to fit can be a huge pain.
There are alternative, less widely known techniques out there with different tradeoffs. For example, if you can make the original program bytes relocatable then you can run those through a linker and let it mend both original and new parts together. I've built tooling to help do that specifically (delinking programs back to object files [1]), it's faster than a decompilation project because you merely need to figure out and undo all the relocation spots, but you don't get source code at the end.
* not designed to be compiled again. It most likely won't compile without serious manual fixing. For example, decompilers often insert "pseudo-functions" to denote that something not easily representable in C is happening. Like CONCAT(var1, var2) may mean that both var1 and var2 are used as a single variable obtained by concatenating their bits (in practice: AX is used when AH and AL were already determined to be variables). Similar intrinsics exist for carry bits, jumps to arbitrary pointers, etc. This sometimes means that type inference went wrong somewhere, which brings us to...
* not perfect, and not designed to be perfect. Decompiler has no idea if a variable on stack is a qword or 8 byte array, it can only guess from local usage. In many cases array will be incorrectly decompiled as a single variable, or pointer parameter as an integer. This is not a huge problem when reversing, but catastrophic for decompilation. Automated struct identification is even harder, often almost impossible when you take unions into account. As a reverse engineer you are supposed to fix that interactively during analysis.
* decompilers in general don't decompile global data definitions - you interpret memory using a separate view. And for a good reason - what may look to the decompiler like three consecutive independent variables may actually be an (unrecognized) structure that must be kept in the same exact layout. Defining them as three independent C variables would almost surely not work.
* for firmware in particular the binary may be required to follow a specific layout or have other unusual characteristics (like volatile memory regions). No decompiler will give you a correct linker script to use for recompilation.
These problems are just out of the top of my mind. Worth noting that a new and upcoming reversing tool called rev.ng actually has working recompilation from C as one of their planned features, so we'll see what they come up with (and if they succeed).