mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
1071 lines
25 KiB
C++
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();
|
|
}
|
|
}
|
|
}
|