BizHawk icon indicating copy to clipboard operation
BizHawk copied to clipboard

Fix on bus read issue (#3813)

Open VelpaChallenger opened this issue 8 months ago • 8 comments

For BSNES core, signatures were updated and data value was passed to the read callback after reading from the bus. For genplus-gx, since all callbacks (read/write/exec) use the same signature from mem_cb, I had to pass the value to all three of them. And since this could potentially cause some unwanted behavior, I also updated the values passed to the write and exec callbacks inside the core (I divided each update in a separate commit so it's easier to follow and understand).

Check if completed:

VelpaChallenger avatar Nov 05 '23 18:11 VelpaChallenger

Something important to keep in mind is that there's a trade-off to these changes, and that is that a memory.writebyte inside a memory read callback function will not write the value before the reading by the core, so if someone wanted to try to force a value to be read by the game (as a form of stronger freezing if you will), it won't be possible anymore, since now the callback is called after the bus is read (albeit still before the game reads it). I don't know how many people actually do this, but just as a heads-up.

VelpaChallenger avatar Nov 12 '23 15:11 VelpaChallenger

That's actually a valid point, and it reinforces the point that there IS a "before" and "after" state when it comes to reading values, and the decision on where the callback is called has an actual impact on how that callback can be used.

Quoting myself from earlier:

I'd definitely appreciate an update to the docs to clarify for both bizhawk devs and lua devs how exactly those functions should work.

@YoshiRulz I do believe the "before" part for the callback is correct and matches expectations, we just need to decide what to do with the val parameter and how it should get populated: https://github.com/TASEmulators/BizHawk/blob/6e77c7348f3c29722fbdae8040085390c9f03a17/src/BizHawk.Client.Common/lua/LuaHelperLibs/EventsLuaLibrary.cs#L206

Morilli avatar Nov 12 '23 23:11 Morilli

I'd definitely appreciate an update to the docs to clarify for both bizhawk devs and lua devs how exactly those functions should work.

[...] we just need to decide what to do with the val parameter and how it should get populated

I don't understand your question.

Fires immediately before the given address is read by the core. [...] val is the value read.

...seems pretty clear to me. Are you saying we should make that even clearer, maybe "val is the value to be read"? Or is it that existing implementations don't match the docs?


so if someone wanted to try to force a value to be read by the game (as a form of stronger freezing if you will), it won't be possible anymore

In our discussions on Discord re: possible implementations, this never came up. Freezing needs to remain functional—and I believe if you've broken it for Lua then it must also be broken for cheats.

YoshiRulz avatar Nov 12 '23 23:11 YoshiRulz

Are you saying we should make that even clearer, maybe "val is the value to be read"? Or is it that existing implementations don't match the docs?

Both, probably. First of all yes, val is the value to be read is the only way that makes sense to me. Also, I assume no core will actually implement both conditions "correctly" at the moment, aka call the callback before the value is read (and allow for manipulation of the value to be read before it IS read), and also provide the value to be read in the callback.

Making it clearer in the docs what's important would help here.

Morilli avatar Nov 13 '23 00:11 Morilli

In our discussions on Discord re: possible implementations, this never came up. Freezing needs to remain functional—and I believe if you've broken it for Lua then it must also be broken for cheats.

I think there's a misunderstanding: freezing still works as usual and hasn't been affected in any way. What I meant with the wording "stronger freezing" is that when you freeze a memory address using cheats (traditional way), you are not actually freezing the memory address in the strict sense of the word, or at least not the way I'd expect. Whenever a frame advances, if the game's code updates the memory address, it will be updated. Say for example that you freeze 0xC676 when it has stored a 0xC value, if there's a line like move.b D0, ($C676), and then there's another line move.b ($C676), ($C284), then 0xC284 is not going to be populated with the value 0xC, rather it's going to be populated with whatever value was in D0. That's what I mean when I say it's not a freeze in the strict sense of the word. It's only going to get updated to 0xC at the end of the frame, but during it, freezing has no effect. However, when using memory callbacks, you can do something like memory.writebyte(0xC676, 0xC) inside your callback and then when move.b ($C676), ($C284) is executed, the value 0xC is going to be passed to 0xC284 because that is the value that is going to be read at 0xC676.

Also, the way I see it, there are 3 key moments to have in mind:

  1. The moment the bus is read
  2. The moment the game reads the value
  3. The moment the callback is run

The moment the bus is read is for BSNES when this line auto data = bus.read(address, r.mdr); is executed. The moment the game reads the value is after the function used for accessing memory values returns (for BSNES, that would be when CPU::read returns). And the moment the callback is run is, well, when it's called by the core.

My point being that as I mentioned in the issue comments, while I agree with Morilli that the decision on where the callback is called has an actual impact on how that callback can be used, I still don't think there is really a "before" and an "after" state. The value still hasn't been sent when the callback is run, in fact, one possible way to go around this is to call bus.read again after calling the callback and then send that as the return value of CPU::read (for BSNES). That would be possible because the game's code still didn't receive the value at that point in time, so it's still before.

I do believe before the given address is read by the core should be changed to before the given address is read by the game's code, though. val is the value to be read sounds more accurate, but it's still not fully accurate given what we are discussing here and how memory callbacks cannot be used for the stronger freezing I was talking about.

Also, speaking of which. This detail slipped my mind earlier, but for the existing implementations, like here: https://github.com/TASEmulators/BizHawk/blob/b98942dcfdb7acbb39acc2864253d2bf55132fda/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs#L1077-L1084 memory callbacks already cannot be used for the stronger freezing. So in that sense, nothing to really worry about. (As a positive of this implementation, you can know how many bytes are actually being read by the game (so val will be 1 byte if just 1 byte is being read, 2 bytes if 2 bytes are being read, etc.). This has been very useful to me for reverse engineering.)

VelpaChallenger avatar Nov 13 '23 01:11 VelpaChallenger

This discussion is continuing on Discord, but I want to apologise for my earlier assertion that you'd broken freezing. Freezing is, of course, already "broken" in that sense, and has been forever since it's implemented as pokes. https://github.com/TASEmulators/BizHawk/blob/bb0d8f231d802dc6e4d3b09426e74435c9e23c37/src/BizHawk.Client.Common/tools/Cheat.cs#L168-L176 (This method is called twice in StepRunLoop_Core, both before and after FrameAdvance.)

YoshiRulz avatar Nov 16 '23 08:11 YoshiRulz