nestopia/source/win32/NstManagerNetplay.cpp

1071 lines
25 KiB
C++

////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Nestopia is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
////////////////////////////////////////////////////////////////////////////////////////
#include "NstIoLog.hpp"
#include "NstIoScreen.hpp"
#include "NstSystemDll.hpp"
#include "NstResourceString.hpp"
#include "NstWindowParam.hpp"
#include "NstSystemThread.hpp"
#include "NstManagerPaths.hpp"
#include "NstDialogNetplay.hpp"
#include "NstManagerNetplay.hpp"
#include "../kaillera/kailleraclient.h"
#include "../core/api/NstApiMachine.hpp"
#include "../core/api/NstApiFds.hpp"
#include <Shlwapi.h>
#ifndef WM_THEMECHANGED
#define WM_THEMECHANGED WM_NULL
#endif
namespace Nestopia
{
namespace Managers
{
class Netplay::Dll : System::Dll
{
typedef int (WINAPI *GetVersionFunc)(char*);
typedef int (WINAPI *InitFunc)();
typedef int (WINAPI *ShutdownFunc)();
typedef int (WINAPI *SetInfosFunc)(kailleraInfos*);
typedef int (WINAPI *SelectServerDialogFunc)(HWND);
typedef int (WINAPI *ModifyPlayValuesFunc)(void*,int);
typedef int (WINAPI *ChatSendFunc)(char*);
typedef int (WINAPI *EndGameFunc)();
InitFunc const Init;
ShutdownFunc const Shutdown;
public:
using System::Dll::operator !;
GetVersionFunc const GetVersion;
SetInfosFunc const SetInfos;
SelectServerDialogFunc const SelectServerDialog;
ModifyPlayValuesFunc const ModifyPlayValues;
ChatSendFunc const ChatSend;
EndGameFunc const EndGame;
Dll()
:
System::Dll (L"kailleraclient.dll"),
Init (Fetch< InitFunc >( "_kailleraInit@0" )),
Shutdown (Fetch< ShutdownFunc >( "_kailleraShutdown@0" )),
GetVersion (Fetch< GetVersionFunc >( "_kailleraGetVersion@4" )),
SetInfos (Fetch< SetInfosFunc >( "_kailleraSetInfos@4" )),
SelectServerDialog (Fetch< SelectServerDialogFunc >( "_kailleraSelectServerDialog@4" )),
ModifyPlayValues (Fetch< ModifyPlayValuesFunc >( "_kailleraModifyPlayValues@8" )),
ChatSend (Fetch< ChatSendFunc >( "_kailleraChatSend@4" )),
EndGame (Fetch< EndGameFunc >( "_kailleraEndGame@0" ))
{
if
(
GetVersion &&
Init &&
Shutdown &&
SetInfos &&
SelectServerDialog &&
ModifyPlayValues &&
ChatSend &&
EndGame
)
Init();
else
Unload();
}
~Dll()
{
if (*this)
Shutdown();
}
};
class Netplay::Kaillera : Manager
{
public:
Kaillera(Emulator&,Window::Menu&,const Paths&,Window::Custom&,bool);
~Kaillera();
enum Exception
{
ERR_LOAD
};
void ToggleConnection();
void Chat();
bool Close() const;
private:
enum
{
MAX_PLAYERS = 8,
MASTER = 1,
WM_NST_OPEN_CLIENT = WM_APP + 57,
WM_NST_CLOSE_CLIENT = WM_APP + 58,
WM_NST_START_GAME = WM_APP + 59
};
class Command
{
enum
{
PACKET_TYPE = 0x0F,
PACKET_DATA = 0xF0,
PACKET_DATA_REGION_PAL = 0x01,
PACKET_DATA_ADAPTER_FAMICOM = 0x02,
PACKET_DATA_SHIFT = 4,
PACKET_STARTUP = 1,
PACKET_RESET = 2,
PACKET_INSERT_DISK = 3,
PACKET_EJECT_DISK = 4,
PACKET_INSERT_COIN = 5
};
uint command;
struct
{
Nes::Input::UserData data;
Nes::Input::Controllers::VsSystem::PollCallback code;
} coinCallback;
struct
{
bool regionPal;
bool adapterFamicom;
bool unlimSprites;
} settings;
public:
void Begin()
{
if (Nes::Machine(instance->emulator).Is(Nes::Machine::VS))
{
Nes::Input::Controllers::VsSystem::callback.Get( coinCallback.code, coinCallback.data );
Nes::Input::Controllers::VsSystem::callback.Unset();
}
else
{
coinCallback.code = NULL;
coinCallback.data = NULL;
}
settings.regionPal = (Nes::Machine(instance->emulator).GetMode() == Nes::Machine::PAL);
settings.adapterFamicom = (Nes::Input(instance->emulator).GetConnectedAdapter() == Nes::Input::ADAPTER_FAMICOM);
settings.unlimSprites = Nes::Video(instance->emulator).AreUnlimSpritesEnabled();
Nes::Video(instance->emulator).EnableUnlimSprites( false );
if (instance->network.player == MASTER)
{
command = PACKET_STARTUP;
if (settings.regionPal)
command |= uint(PACKET_DATA_REGION_PAL) << PACKET_DATA_SHIFT;
if (settings.adapterFamicom)
command |= uint(PACKET_DATA_ADAPTER_FAMICOM) << PACKET_DATA_SHIFT;
}
else
{
command = 0;
}
}
void Send(Emulator::Command input,uint state)
{
NST_COMPILE_ASSERT( Emulator::NUM_COMMANDS == 3 );
NST_VERIFY( command == 0 );
if (command == 0 && instance->network.player == MASTER)
{
switch (input)
{
case Emulator::COMMAND_RESET:
command = PACKET_RESET | (state & 0x1) << PACKET_DATA_SHIFT;
break;
case Emulator::COMMAND_DISK_INSERT:
command = PACKET_INSERT_DISK | state << PACKET_DATA_SHIFT;
break;
case Emulator::COMMAND_DISK_EJECT:
command = PACKET_EJECT_DISK;
break;
}
}
}
NST_FORCE_INLINE uint GetCode()
{
if (instance->network.player != MASTER)
return 0;
if (coinCallback.code)
{
Nes::Input::Controllers::VsSystem vs;
coinCallback.code( coinCallback.data, vs );
if (vs.insertCoin & (Nes::Input::Controllers::VsSystem::COIN_1|Nes::Input::Controllers::VsSystem::COIN_2))
{
return PACKET_INSERT_COIN |
(
((vs.insertCoin & Nes::Input::Controllers::VsSystem::COIN_1) ? (0x1U << PACKET_DATA_SHIFT) : 0) |
((vs.insertCoin & Nes::Input::Controllers::VsSystem::COIN_2) ? (0x2U << PACKET_DATA_SHIFT) : 0)
);
}
}
const uint code = command;
command = 0;
return code;
}
NST_FORCE_INLINE bool Dispatch(const uint packet,Nes::Input::Controllers& controllers)
{
if (packet)
{
const uint data = packet >> PACKET_DATA_SHIFT;
switch (packet & PACKET_TYPE)
{
case PACKET_INSERT_COIN:
controllers.vsSystem.insertCoin =
(
((data & 0x1) ? Nes::Input::Controllers::VsSystem::COIN_1 : 0U) |
((data & 0x2) ? Nes::Input::Controllers::VsSystem::COIN_2 : 0U)
);
break;
case PACKET_RESET:
Nes::Machine(instance->emulator).Reset( data & 0x1 );
break;
case PACKET_INSERT_DISK:
Nes::Fds(instance->emulator).InsertDisk( data / 2, data % 2 );
break;
case PACKET_EJECT_DISK:
Nes::Fds(instance->emulator).EjectDisk();
break;
case PACKET_STARTUP:
Nes::Machine(instance->emulator).SetMode( (data & PACKET_DATA_REGION_PAL) ? Nes::Machine::PAL : Nes::Machine::NTSC );
Nes::Input(instance->emulator).ConnectAdapter( (data & PACKET_DATA_ADAPTER_FAMICOM) ? Nes::Input::ADAPTER_FAMICOM : Nes::Input::ADAPTER_NES );
break;
default:
NST_DEBUG_MSG("unknown netplay package");
break;
}
return true;
}
return false;
}
void End()
{
if (coinCallback.code)
{
Nes::Input::Controllers::VsSystem::callback.Set( coinCallback.code, coinCallback.data );
coinCallback.code = NULL;
coinCallback.data = NULL;
}
Nes::Machine(instance->emulator).SetMode( settings.regionPal ? Nes::Machine::PAL : Nes::Machine::NTSC );
Nes::Input(instance->emulator).ConnectAdapter( settings.adapterFamicom ? Nes::Input::ADAPTER_FAMICOM : Nes::Input::ADAPTER_NES );
Nes::Video(instance->emulator).EnableUnlimSprites( settings.unlimSprites );
}
};
class Input
{
struct
{
Nes::Input::UserData data;
Nes::Input::Controllers::Pad::PollCallback code;
} pollCallback;
public:
void Capture()
{
Nes::Input::Controllers::Pad::callback.Get( pollCallback.code, pollCallback.data );
Nes::Input::Controllers::Pad::callback.Unset();
NST_ASSERT( pollCallback.code );
}
NST_FORCE_INLINE uint GetCode() const
{
uint index = instance->network.player - 1U;
if (index < 4)
{
index = Nes::Input(instance->emulator).GetConnectedController(index) - uint(Nes::Input::PAD1);
NST_VERIFY( index < 4 );
if (index < 4)
{
Nes::Input::Controllers::Pad pad;
pollCallback.code( pollCallback.data, pad, index );
return pad.buttons;
}
}
return 0;
}
NST_FORCE_INLINE void Dispatch(uint port,uint packet,Nes::Input::Controllers& controllers) const
{
NST_ASSERT( port < 4 );
uint index = Nes::Input(instance->emulator).GetConnectedController(port) - uint(Nes::Input::PAD1);
NST_VERIFY( index < 4 );
if (index < 4)
controllers.pad[index].buttons = packet;
}
void Release() const
{
Nes::Input::Controllers::Pad::callback.Set( pollCallback.code, pollCallback.data );
}
};
friend class Command;
friend class Input;
struct Callbacks;
class Client;
void Disconnect();
void StartNetwork(System::Thread::Terminator);
ibool OnOpenClient (Window::Param&);
ibool OnCloseClient (Window::Param&);
ibool OnStartGame (Window::Param&);
ibool OnEnable (Window::Param&);
void OnEmuFrame (Nes::Input::Controllers&);
void OnEmuCommand (Emulator::Command,Emulator::Data);
void OnEmuEvent (Emulator::Event,Emulator::Data);
const Dll dll;
Window::Custom& window;
Window::Netplay::Chat chat;
Window::Netplay dialog;
System::Thread thread;
Window::MsgHandler::Callback enableCallback;
struct
{
bool connected;
Command command;
Input input;
uint player;
uint players;
Path game;
} network;
static Kaillera* instance;
public:
bool ShouldGoFullscreen() const
{
return dialog.ShouldGoFullscreen();
}
void SaveFile() const
{
dialog.SaveFile();
}
};
struct Netplay::Kaillera::Callbacks
{
static int WINAPI Start(char* game,int player,int players)
{
if (game && *game && player > 0 && players > 0 && player-1U < players)
{
instance->network.game = game;
instance->network.player = player;
instance->network.players = players;
instance->window.Post( WM_NST_START_GAME );
}
else
{
instance->dll.EndGame();
NST_DEBUG_MSG("Kaillera::Start() failed!");
}
return 0;
}
static void WINAPI ClientDrop(char* nick,int playerNum)
{
static const HeapString player( HeapString() << Resource::String(IDS_TEXT_PLAYER) << ' ' );
static const HeapString droppedOut( HeapString() << ") " << Resource::String(IDS_TEXT_DROPPEDOUT) );
if (nick && *nick)
Io::Screen() << player << playerNum << " (" << HeapString().Import(nick) << droppedOut;
}
static void WINAPI ChatRecieve(char* nick,char* text)
{
static const HeapString says( HeapString() << ' ' << Resource::String(IDS_TEXT_SAYS) << ": " );
if (nick && *nick && text && *text)
Io::Screen() << HeapString().Import(nick) << says << HeapString().Import(text);
}
};
class Netplay::Kaillera::Client
{
// Uses a hook for monitoring the Kaillera windows activity.
// The bug seems to be located in the Kaillera code so I have
// to resolve to some dirty hacks to prevent the message queue
// from entering an infinite loop. This will happen if the user
// closes the main server list window while others are open.
struct Instance
{
HHOOK hHook;
DWORD threadId;
Instance()
: hHook(NULL) {}
};
DWORD visualStyles;
static Instance instance;
class Callbacks
{
static NST_NO_INLINE bool IsKaillera(const Window::Generic window)
{
HeapString name;
window.Text() >> name;
return
(
(name.Length() >= 8 && name(0,8) == L"Kaillera" ) ||
(name.Length() >= 6 && name(0,6) == L"Anti3D" )
);
}
public:
static BOOL CALLBACK Destroy(HWND hWnd,LPARAM)
{
if (IsKaillera( hWnd ))
::SendMessage( hWnd, WM_SYSCOMMAND, SC_CLOSE, 0 );
return true;
}
static BOOL CALLBACK Find(HWND hWnd,LPARAM lParam)
{
if (::GetParent( hWnd ) && IsKaillera( hWnd ))
{
*reinterpret_cast<HWND*>(lParam) = hWnd;
return false;
}
return true;
}
static BOOL CALLBACK Show(HWND hWnd,LPARAM lParam)
{
if (IsKaillera( hWnd ))
::ShowWindow( hWnd, lParam );
return true;
}
};
template<typename T>
static void Enumerate(BOOL (CALLBACK* callback)(HWND,LPARAM),T t)
{
::EnumThreadWindows( instance.threadId, callback, LPARAM(t) );
}
static bool IsZombie(HWND hWnd)
{
if (!::GetParent( hWnd ))
{
hWnd = NULL;
Enumerate( Callbacks::Find, &hWnd );
if (hWnd)
return true;
}
return false;
}
static LRESULT CALLBACK MessageSpy(int iCode,WPARAM wParam,LPARAM lParam)
{
if (iCode == HC_ACTION)
{
MSG& msg = *reinterpret_cast<MSG*>(lParam);
if (msg.message == WM_CLOSE && IsZombie( msg.hwnd ))
msg.message = WM_NULL;
}
return ::CallNextHookEx( instance.hHook, iCode, wParam, lParam );
}
void DisableVisualStyles()
{
// Kaillera doesn't like XP Visual Styles
struct ComCtl32
{
static bool IsVersion6()
{
const System::Dll comctl32( L"comctl32.dll" );
if (DLLGETVERSIONPROC const getVersion = comctl32.Fetch<DLLGETVERSIONPROC>("DllGetVersion"))
{
DLLVERSIONINFO info;
info.cbSize = sizeof(info);
return getVersion( &info ) == NOERROR && info.dwMajorVersion >= 6;
}
return false;
}
};
visualStyles = 0;
static const bool isVersion6 = ComCtl32::IsVersion6();
if (isVersion6)
{
const System::Dll uxtheme( L"uxtheme.dll" );
typedef DWORD (STDAPICALLTYPE* GetProperty)();
typedef void (STDAPICALLTYPE* SetProperty)(DWORD);
if (GetProperty const getProperty = uxtheme.Fetch<GetProperty>("GetThemeAppProperties"))
{
visualStyles = getProperty();
if (visualStyles)
{
if (SetProperty const setProperty = uxtheme.Fetch<SetProperty>("SetThemeAppProperties"))
{
setProperty( 0 );
Application::Instance::GetMainWindow().Post( WM_THEMECHANGED );
}
}
}
}
}
void RestoreVisualStyles()
{
if (visualStyles)
{
const System::Dll uxtheme( L"uxtheme.dll" );
typedef void (STDAPICALLTYPE* SetProperty)(DWORD);
if (SetProperty const setProperty = uxtheme.Fetch<SetProperty>("SetThemeAppProperties"))
{
setProperty( visualStyles );
visualStyles = 0;
Application::Instance::GetMainWindow().Post( WM_THEMECHANGED );
}
}
}
public:
NST_NO_INLINE Client()
{
NST_ASSERT( !instance.hHook );
instance.threadId = ::GetCurrentThreadId();
instance.hHook = ::SetWindowsHookEx
(
WH_GETMESSAGE,
MessageSpy,
::GetModuleHandle(NULL),
instance.threadId
);
if (!instance.hHook)
throw "SetWindowsHookEx() failed!";
Kaillera::instance->window.Post( WM_NST_OPEN_CLIENT );
DisableVisualStyles();
}
static void Run()
{
Kaillera::instance->dll.SelectServerDialog( NULL );
}
static void Show()
{
if (instance.hHook)
Enumerate( Callbacks::Show, SW_SHOW );
}
static void Hide()
{
if (instance.hHook)
Enumerate( Callbacks::Show, SW_HIDE );
}
static void Close()
{
if (instance.hHook)
Enumerate( Callbacks::Destroy, 0 );
}
NST_NO_INLINE ~Client()
{
NST_ASSERT( instance.hHook );
::UnhookWindowsHookEx( instance.hHook );
instance.hHook = NULL;
RestoreVisualStyles();
Kaillera::instance->window.Post( WM_NST_CLOSE_CLIENT );
}
};
Netplay::Kaillera* Netplay::Kaillera::instance = NULL;
Netplay::Kaillera::Client::Instance Netplay::Kaillera::Client::instance;
Netplay::Kaillera::Kaillera
(
Emulator& e,
Window::Menu& m,
const Paths& paths,
Window::Custom& w,
const bool doFullscreen
)
:
Manager ( e, m, this, &Kaillera::OnEmuEvent ),
window ( w ),
chat ( dll.ChatSend ),
dialog ( e, paths, doFullscreen )
{
if (!dll)
throw ERR_LOAD;
NST_ASSERT( instance == NULL );
instance = this;
}
Netplay::Kaillera::~Kaillera()
{
instance = NULL;
}
void Netplay::Kaillera::StartNetwork(System::Thread::Terminator)
{
Client().Run();
}
ibool Netplay::Kaillera::OnOpenClient(Window::Param&)
{
network.input.Capture();
emulator.BeginNetplayMode();
return true;
}
ibool Netplay::Kaillera::OnCloseClient(Window::Param&)
{
network.input.Release();
window.Messages().Remove( this );
emulator.EndNetplayMode();
return true;
}
bool Netplay::Kaillera::Close() const
{
if (emulator.NetPlayers())
{
if (emulator.IsImage())
emulator.Unload();
else
Client::Close();
return false;
}
return true;
}
ibool Netplay::Kaillera::OnStartGame(Window::Param&)
{
emulator.StartNetplay
(
this,
&Kaillera::OnEmuFrame,
&Kaillera::OnEmuCommand,
network.player,
network.players
);
Application::Instance::GetMainWindow().Send
(
Application::Instance::WM_NST_LAUNCH,
Paths::File::GAME|Paths::File::ARCHIVE,
dialog.GetPath(network.game.Ptr())
);
if (!emulator.IsOn())
{
emulator.StopNetplay();
dll.EndGame();
}
return true;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("t", on)
#endif
void Netplay::Kaillera::OnEmuFrame(Nes::Input::Controllers& controllers)
{
controllers.vsSystem.insertCoin = 0;
if (network.connected)
{
uchar packets[MAX_PLAYERS][2] = {{0},{0}};
packets[0][0] = network.input.GetCode();
packets[0][1] = network.command.GetCode();
if (dll.ModifyPlayValues( packets, 2 ) != -1)
{
network.command.Dispatch( packets[0][1], controllers );
for (uint i=0, n=NST_MIN(4,network.players); i < n; ++i)
network.input.Dispatch( i, packets[i][0], controllers );
return;
}
}
network.connected = false;
window.PostCommand( IDM_NETPLAY_CONNECTION );
}
void Netplay::Kaillera::OnEmuCommand(Emulator::Command command,Emulator::Data data)
{
if (network.connected)
network.command.Send( command, data );
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
void Netplay::Kaillera::Chat()
{
chat.Open();
}
void Netplay::Kaillera::ToggleConnection()
{
if (emulator.NetPlayers())
{
Close();
}
else if (!emulator.IsImage() && dialog.Open())
{
static const Window::MsgHandler::Entry<Kaillera> messages[] =
{
{ WM_NST_OPEN_CLIENT, &Kaillera::OnOpenClient },
{ WM_NST_CLOSE_CLIENT, &Kaillera::OnCloseClient },
{ WM_NST_START_GAME, &Kaillera::OnStartGame }
};
window.Messages().Add( this, messages );
NST_ASSERT( dialog.GetGamePaths().size() );
String::Heap<char> strings;
for (Window::Netplay::GamePaths::const_iterator it(dialog.GetGamePaths().begin()), end(dialog.GetGamePaths().end()); it != end; ++it)
strings << it->Target().File() << '\0';
String::Heap<char> name;
name << "Nestopia " << Application::Instance::GetVersion();
kailleraInfos info;
info.appName = name.Ptr();
info.gameList = strings.Ptr();
info.gameCallback = Callbacks::Start;
info.chatReceivedCallback = Callbacks::ChatRecieve;
info.clientDroppedCallback = Callbacks::ClientDrop;
info.moreInfosCallback = NULL;
dll.SetInfos( &info );
thread.Start( System::Thread::Callback(this,&Kaillera::StartNetwork) );
}
}
ibool Netplay::Kaillera::OnEnable(Window::Param& param)
{
if (!param.wParam)
window.Send( WM_ENABLE, true, 0 );
return true;
}
void Netplay::Kaillera::OnEmuEvent(const Emulator::Event event,Emulator::Data)
{
if (emulator.NetPlayers())
{
static Window::MsgHandler::Callback old;
switch (event)
{
case Emulator::EVENT_POWER_ON:
if (emulator.IsGame())
{
Client::Hide();
network.connected = true;
network.command.Begin();
menu[IDM_NETPLAY_CHAT].Enable();
if (dialog.ShouldGoFullscreen())
window.SendCommand( IDM_VIEW_SWITCH_SCREEN );
enableCallback = window.Messages()[WM_ENABLE].Replace( this, &Kaillera::OnEnable );
}
break;
case Emulator::EVENT_POWER_OFF:
if (emulator.IsGame())
{
network.connected = false;
network.command.End();
menu[IDM_NETPLAY_CHAT].Disable();
chat.Close();
if (dialog.ShouldGoFullscreen())
window.SendCommand( IDM_VIEW_SWITCH_SCREEN );
window.Messages()[WM_ENABLE] = enableCallback;
Client::Show();
dll.EndGame();
}
break;
case Emulator::EVENT_UNLOAD:
emulator.StopNetplay();
break;
}
}
}
Netplay::Netplay
(
Emulator& e,
const Configuration& cfg,
Window::Menu& m,
const Paths& p,
Window::Custom& w
)
:
Manager ( e, m, this, &Netplay::OnEmuEvent ),
kaillera ( NULL ),
window ( w ),
paths ( p ),
fullscreen ( false ),
doFullscreen ( cfg["netplay"]["fullscreen"].Yes() )
{
menu[IDM_NETPLAY_CONNECTION].Text() << Resource::String( IDS_MENU_NETPLAY_CONNECT );
menu[IDM_NETPLAY_CHAT].Disable();
const Dll dll;
Io::Log log;
if (!dll)
{
log << "Kaillera: file \"kailleraclient.dll\" not found or initialization failed. "
"netplay will be disabled!\r\n";
menu[IDM_NETPLAY_CONNECTION].Disable();
}
else
{
Application::Instance::Events::Add( this, &Netplay::OnAppEvent );
static const Window::Menu::CmdHandler::Entry<Netplay> commands[] =
{
{ IDM_NETPLAY_CONNECTION, &Netplay::OnCmdConnection },
{ IDM_NETPLAY_CHAT, &Netplay::OnCmdChat }
};
menu.Commands().Add( this, commands );
char version[16];
version[0] = '\0';
dll.GetVersion( version );
version[15] = '\0';
if (*version)
{
log << "Kaillera: found \"kailleraclient.dll\" version " << version << "\r\n";
if (std::strcmp( version, "0.9" ))
log << "Kaillera: warning, the DLL file may be incompatible with Nestopia!\r\n";
}
else
{
log << "Kaillera: warning, unknown version of \"kailleraclient.dll\"!\r\n";
}
UpdateMenu();
}
}
Netplay::~Netplay()
{
Application::Instance::Events::Remove( this );
delete kaillera;
}
void Netplay::Save(Configuration& cfg,const bool saveGameList) const
{
cfg["netplay"]["fullscreen"].YesNo() = (kaillera ? kaillera->ShouldGoFullscreen() : doFullscreen);
if (kaillera && saveGameList)
kaillera->SaveFile();
}
bool Netplay::Close() const
{
return kaillera ? kaillera->Close() : true;
}
void Netplay::UpdateMenu() const
{
menu[IDM_NETPLAY_CONNECTION].Enable
(
emulator.NetPlayers() || (!fullscreen && !emulator.IsImage())
);
}
void Netplay::OnEmuEvent(const Emulator::Event event,const Emulator::Data data)
{
switch (event)
{
case Emulator::EVENT_NETPLAY_MODE:
menu[IDM_NETPLAY_CONNECTION].Text() << Resource::String( data ? IDS_MENU_NETPLAY_DISCONNECT : IDS_MENU_NETPLAY_CONNECT );
case Emulator::EVENT_LOAD:
case Emulator::EVENT_UNLOAD:
UpdateMenu();
break;
}
}
void Netplay::OnAppEvent(Application::Instance::Event event,const void*)
{
switch (event)
{
case Application::Instance::EVENT_DESKTOP:
case Application::Instance::EVENT_FULLSCREEN:
fullscreen = (event == Application::Instance::EVENT_FULLSCREEN);
UpdateMenu();
break;
}
}
void Netplay::OnCmdConnection(uint)
{
if (kaillera == NULL)
{
try
{
kaillera = new Kaillera( emulator, menu, paths, window, doFullscreen );
}
catch (Kaillera::Exception)
{
return;
}
}
kaillera->ToggleConnection();
}
void Netplay::OnCmdChat(uint)
{
if (kaillera)
kaillera->Chat();
}
}
}