- Joined
- Jan 29, 2008
- Messages
- 2,330
- Trophies
- 1
- Age
- 51
- Location
- Ya Cant Get There From Here
- Website
- www.backwoodzstudioz.com
- XP
- 2,725
- Country
-
Confirmedthank you, Drenn! DSi mode working excellent now=D
It's basically a way to avoid needing to synchronise link-cable data over WiFi when latencies are high. You shift all the work to the link initialisation phase and then it becomes pain-free to send data, because the link cable data is generated in the very same Nintendo DS.^ Didn't get half of that ...but sounds awesome![]()
For link cable emulation, what you could do is emulate both Game Boys on each DS.
That way, all of the information that is missing for the link cable session is the keys pressed on the other Game Boy, which is provided by the other DS. The link cable can then be emulated locally, as if the other Game Boy had sent what it needed to send given its global state and the keys pressed on it.
The link initialisation would be as follows:
1. Pause both GBs at the next vblank, collapse cached flags, etc.
2. Synchronise the ROM such that "GB B" on DS A gets a copy of the ROM on GB B on DS B. Similarly, synchronise the ROM such that "GB A" on DS B gets a copy of the ROM on GB A on DS A. This step may require memory allocations or the creation of a scratch file on both DSes, and may be helped by global hashes and block hashes to avoid sending an entire ROM. From this point on, each DS emulates both its own ROM (showing its progress on-screen) and the ROM of its link slave (hiding its progress on-screen).
3. Synchronise all other emulation variables such that "GB B" on DS A gets a copy of the state of GB B on DS B. Similarly, synchronise all other emulation variables such that "GB A" on DS B gets a copy of the state of DS A. This step could be implemented by writing a saved state and the SRAM in memory, then sending it to the other DS perhaps with compression. From this point on, each DS can see the progress of both its Game Boy and the other DS's Game Boy.
4. End link initialisation. Enter "link mode".
In link mode:
1. The controls pressed on GB A of DS A are sent to DS B at the beginning of each frame, so that DS B can be made aware of how GB A will advance for the frame, and vice-versa. Each DS waits for the other GB's keypresses, which can be encoded in one byte. Each DS ignores keypresses until its own Game Boy has emulated one frame, so as to keep the synchronisation (e.g. you can't press A and have it apply in the middle of a frame anymore, because the other Game Boy will never know).
2. Each DS also emulates a single frame of each Game Boy, given the controls pressed at the beginning of their respective frames. The control passes from one Game Boy to the other when a byte is sent on the cable, so that the other Game Boy can receive the byte that was just sent.
3. Show the screen and sound only for the Game Boy respective to each DS.
That way, the link cable emulation can simply be 1 byte per frame! Each Game Boy will advance correctly, given that it has the memory contents of the other at all times, so you can press A on one Game Boy then A on the other to trade a Pokémon (which the other Game Boy will know about, so it can send the right bytes on the pretend cable), or the arrow keys in Tetris to move a piece (which the other Game Boy can turn into the right data for the link cable).
I take no credit for this idea.
If the ROM used in GB A and the ROM used in GB B are the same, then the combination can only go at 60 FPS if the ROM can fast-forward to 120 FPS (120/2 = 60 FPS). Otherwise, you get slowdown (80/2 = 40 FPS).Does this only work on games that can go at 120 FPS or does it need more power because of link cable emulation or vice versa because you're emulating the same rom twice?
And how do you have it so that the frames are synchronized on each DS? (swiWaitForVBlank(); won't happen at the same time on each of the DS')1. The controls pressed on GB A of DS A are sent to DS B at the beginning of each frame, so that DS B can be made aware of how GB A will advance for the frame, and vice-versa.
Graphical frames. It would wait for vblank, then send keypresses and wait for the other DS's keypresses. So that way, it's more like: (pseudocode)And how do you have it so that the frames are synchronized on each DS? (swiWaitForVBlank(); won't happen at the same time on each of the DS')
Or is this a calculated frame, not a graphical one?
It sounds to much like how Nintendo did Mario Kart DS and NSMB.
How do you do cheats in multiplayer this way?
swiWaitForVBlank();
nifiSendPacketByte(gbKeys);
unsigned char otherGbKeys = nifiWaitForPacket();
runEmul(gbKeys);
runEmulSlaveGb(otherGbKeys);
Cool idea! The trickiest part of all this would be going back over all my code to allow for more than 1 emulated gameboy, as you probably know this will take some reworking. It'd probably be worth it though!For link cable emulation, what you could do is emulate both Game Boys on each DS.
That way, all of the information that is missing for the link cable session is the keys pressed on the other Game Boy, which is provided by the other DS. The link cable can then be emulated locally, as if the other Game Boy had sent what it needed to send given its global state and the keys pressed on it.
The link initialisation would be as follows:
1. Pause both GBs at the next vblank, collapse cached flags, etc.
2. Synchronise the ROM such that "GB B" on DS A gets a copy of the ROM on GB B on DS B. Similarly, synchronise the ROM such that "GB A" on DS B gets a copy of the ROM on GB A on DS A. This step may require memory allocations or the creation of a scratch file on both DSes, and may be helped by global hashes and block hashes to avoid sending an entire ROM. From this point on, each DS emulates both its own ROM (showing its progress on-screen) and the ROM of its link slave (hiding its progress on-screen).
3. Synchronise all other emulation variables such that "GB B" on DS A gets a copy of the state of GB B on DS B. Similarly, synchronise all other emulation variables such that "GB A" on DS B gets a copy of the state of DS A. This step could be implemented by writing a saved state and the SRAM in memory, then sending it to the other DS perhaps with compression. From this point on, each DS can see the progress of both its Game Boy and the other DS's Game Boy.
4. End link initialisation. Enter "link mode".
In link mode:
1. The controls pressed on GB A of DS A are sent to DS B at the beginning of each frame, so that DS B can be made aware of how GB A will advance for the frame, and vice-versa. Each DS waits for the other GB's keypresses, which can be encoded in one byte. Each DS ignores keypresses until its own Game Boy has emulated one frame, so as to keep the synchronisation (e.g. you can't press A and have it apply in the middle of a frame anymore, because the other Game Boy will never know).
2. Each DS also emulates a single frame of each Game Boy, given the controls pressed at the beginning of their respective frames. The control passes from one Game Boy to the other when a byte is sent on the cable, so that the other Game Boy can receive the byte that was just sent.
3. Show the screen and sound only for the Game Boy respective to each DS.
That way, the link cable emulation can simply be 1 byte per frame! Each Game Boy will advance correctly, given that it has the memory contents of the other at all times, so you can press A on one Game Boy then A on the other to trade a Pokémon (which the other Game Boy will know about, so it can send the right bytes on the pretend cable), or the arrow keys in Tetris to move a piece (which the other Game Boy can turn into the right data for the link cable).
I take no credit for this idea.
More ram would definitely help. But I'd prefer to get it running without the extra ram, since many don't own those ram expansions (myself included). But that might not work out for cross-game linking. Speaking of which, I've been looking for an ez-3in1 but I couldn't find any that would fit in a ds phat. I might end up buying a lite 3in1 and trying to modify it to fit in a phat. Apparently people have done that.Maybe support for the 3in1 and official ram expansion will aid in the memory required to do this?
More ram would definitely help. But I'd prefer to get it running without the extra ram, since many don't own those ram expansions (myself included). But that might not work out for cross-game linking. Speaking of which, I've been looking for an ez-3in1 but I couldn't find any that would fit in a ds phat. I might end up buying a lite 3in1 and trying to modify it to fit in a phat. Apparently people have done that.
Yeah, it's all global variables at the moment, but you already have a wealth of functions to do work for you, like saving and loading a state. You could dump those into a class:Cool idea! The trickiest part of all this would be going back over all my code to allow for more than 1 emulated gameboy, as you probably know this will take some reworking. It'd probably be worth it though!
class GameBoy {
private:
u8 vram[...];
u8 rom[MAX_LOADED_ROM_BANKS][...];
u8* romAddress[MAX_ROM_BANKS];
. . .
public:
GameBoy(const GameBoy& a) /* copy constructor */;
GameBoy(const GameBoy& a, const u8* const newRom);
GameBoy(const GameBoy& a, const std::string& newRomFile);
int loadState(const u8* const data);
int saveState(u8* const data) const;
int runUntil(const int interrupt);
void linkTo(const GameBoy& b);
void unlink();
. . .
}
GameBoy b = GameBoy(a); // if the ROM is the same
GameBoy b = GameBoy(a, newRom); // from a memory allocation <= 512 KiB if the ROMs aren't the same
GameBoy b = GameBoy(a, scratchFile); // from a file if the ROM is too large
if (linkMode) {
int aCause, bCause;
do {
aCause = a.runUntil(SERIAL | VBLANK); // pass control to GB B
bCause = b.runUntil(SERIAL | VBLANK); // pass control to GB A
} while (!( aCause == VBLANK || bCause == VBLANK )); // until either has stopped due to VBLANK
if (bCause == SERIAL) // then run the other GB until its VBLANK
bCause = b.runUntil(VBLANK);
else if (aCause == SERIAL)
aCause = a.runUntil(VBLANK);
} else {
a.runUntil(VBLANK);
}
a.render();